Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow interpolation inside of backticks and strings #11

Open
casey opened this issue Oct 31, 2016 · 33 comments
Open

Allow interpolation inside of backticks and strings #11

casey opened this issue Oct 31, 2016 · 33 comments

Comments

@casey
Copy link
Owner

casey commented Oct 31, 2016

To avoid making this a backwards incompatible change, only strings and backticks preceeded by an f will allow interpolation:

bar := "world"
foo := f"Hello, {{bar}}!"
baz :=  f`echo "Hello, {{bar}}"`

Both foo and baz evaluate to Hello, world!.

To keep this simple, we should only support single identifiers in interpolations, i.e., no arbitrary expressions like foo + bar.

@casey

This comment has been minimized.

@casey

This comment has been minimized.

@casey casey closed this as completed Jan 9, 2017
@casey

This comment has been minimized.

@casey casey reopened this Jan 23, 2017
@casey casey added this to the 1.0 milestone May 13, 2017
@casey casey removed this from the 1.0 milestone Apr 17, 2019
@casey

This comment has been minimized.

@casey casey added this to the eventually milestone Apr 17, 2019
@casey casey removed this from the eventually milestone Jul 2, 2020
@casey casey changed the title Allow interpolation inside of double quoted strings Allow interpolation inside of backticks and double quoted strings Oct 9, 2020
@casey casey changed the title Allow interpolation inside of backticks and double quoted strings Allow interpolation inside of backticks and strings Oct 9, 2020
@kwshi
Copy link

kwshi commented May 5, 2021

Interesting how nobody has taken this on yet! I am quite interested in having this and have some basic experience working w/ lexers/parsers for other things, so maybe I'll try to take a look soon-ish and see if I can help make this happen!

@casey
Copy link
Owner Author

casey commented May 6, 2021

I started working on this, in-progress branch here.

I marked this as "good first issue", but I think that's misleading, because it's super hard!

I've gotten lexing and parsing mostly working, but I haven't done evaluation, i.e. actually evaluating the fragments, concatenating them, and, in the case of format backticks, evaluating the result.

If your interested, you could pick up the branch as a starting point. I'm not sure when I'm going to work on it again.

@pyrsmk
Copy link

pyrsmk commented Jan 22, 2022

Is there any news on the subject?

edit: without this I'm forced to go back to make 😅

@casey
Copy link
Owner Author

casey commented Jan 22, 2022

Is there any news on the subject?

This is a big feature, so time to implementation is unknown.

@ngirard
Copy link

ngirard commented Feb 3, 2022

Could this crate be any helpful ?

MiniJinja -- Powerful but minimal dependency template engine for Rust

Announcement on r/rust (2021-09-24).

MiniJinja is a powerful but minimal dependency template engine for Rust which is based on the syntax and behavior of the Jinja2 template engine for Python.

It's implemented on top of serde and only has it as a single dependency. It supports a range of features from Jinja2 including inheritance, filters and more. The goal is that it should be possible to use some templates in Rust programs without the fear of pulling in complex dependencies for a small problem. Additionally it tries not to re-invent something but stay in line with prior art to leverage an already existing ecosystem of editor integrations.

Cheers

@kallangerard
Copy link

Is a format expression an easier compromise? I can live without interpolation if there's a string.format(str, args*) method available.

foo := "github.com"
api_uri := "api/v2"
foo_url := string_format("https://%s/%s", foo, api_uri)

@regmicmahesh
Copy link

i'm hoping for this addition.

@crabmusket
Copy link

crabmusket commented May 14, 2023

I'm working around this using replace:

value := env_var('VALUE')
result := replace('here is _, _ a pair of values', '_', value)

It's acceptable if you're only using one value multiple times like I am; it would get gnarly with multiple values and replacements.

@akiross

This comment was marked as resolved.

@blueforesticarus
Copy link

It's very much not clear in the documentation that you can't do this, currently.
Perhaps a note should be added to the section on back-ticks.

@NickLarsenNZ
Copy link

NickLarsenNZ commented Feb 28, 2024

This is the only thing stopping me being able to convert a whole Makefile over to Just :(

I have managed to build up the command-line using +, but I can't see how I can then execute that for use in a new variable.

For example:

BLAH := some_example # this is the canonical source that we don't want to repeat through the whole file
VERSION := $(shell cargo metadata --format-version 1 | jq -r '.packages[] | select(.name=="my-prefix-${BLAH}") | .version')

I can then convert it to:

export BLAH := "some_example" # this is the canonical source that we don't want to repeat
version_cmd := "cargo metadata --format-version 1 | jq -r '.packages[] | select(.name==\"my-prefix-" + BLAH + "\") | .version'"
export VERSION = `...` # what can I do here to execute version_cmd

@gg718
Copy link

gg718 commented Mar 14, 2024

I'm in the exact same situation as @NickLarsenNZ. Currently on the verge of convincing my organization to migrate over to Justfiles from Makefiles, but this is the final missing piece. I am tracking this issue closely in the hope it is implemented some time soon, otherwise the opportunity to switch will pass and we will be stuck with Make for the foreseeable future, or worst yet a custom an in-house solution.

@casey Apologies to ping you directly, but if you could provide a steer on your appetite to implement this and maybe a ballpark ETA, I can make a more informed decision for my team.

edit: For what it's worth, my preference is Python's f"Hello, {{name}}!" syntax, as mentioned elsewhere. It's backwards-compatible and common enough at this point that it wouldn't be weird to borrow the idea.

@laniakea64
Copy link
Contributor

What kinds of workarounds are people using for the lack of way to use variables in backticks?

Recently ran into a case where variable in backticks would be useful: writing a justfile to build a soft-fork project that's distributed as a set of source code patches. As there maybe multiple upstream source tarball versions available locally, selecting the right one requires a (user-overridable) command. The output of that command (the selected tarball) needs to be stored in a variable for use by recipes. It's also needed in another variable backtick: since the recipes also have to know where the tarball will extract to, a second command is used to get the name of the subdirectory in the tarball. That second command requires the output of the first to know which tarball to work with.

The best I could come up with is making an external Python script to find the correct tarball, with an environment variable override option, and calling it in both backticks. But this is not a great solution: the code is run twice for the same result, and using an environment variable for the override risks build environment pollution.

With backtick interpolation, this would be straightforward, only a few lines in the justfile (probably without any external script), and the override could be done with just --set.

@laniakea64
Copy link
Contributor

One of the reasons that this is hard is because it will be possible to nest interpolations recursively:

qux := f"{{f"{{f"{{bar}}"}}"}}"

If this is what makes this hard, maybe nesting f-strings could be explicitly disallowed, at least initially? Most if not all uses for nesting could be achieved with intermediate variables. Adding nesting support later if needed wouldn't affect backwards compatibility.

@NickLarsenNZ
Copy link

An alternative take is to just make a breaking change allowing interpolation in backticks, and call it v2. Then give instructions to v1 users on how to deal with literals which now have different behavior.

@laniakea64
Copy link
Contributor

An alternative take is to just make a breaking change allowing interpolation in backticks, and call it v2. Then give instructions to v1 users on how to deal with literals which now have different behavior.

https://just.systems/man/en/chapter_9.html

@gyreas
Copy link
Contributor

gyreas commented May 8, 2024

One of the reasons that this is hard is because it will be possible to nest interpolations recursively:

qux := f"{{f"{{f"{{bar}}"}}"}}"

If this is what makes this hard, maybe nesting f-strings could be explicitly disallowed, at least initially? Most if not all uses for nesting could be achieved with intermediate variables. Adding nesting support later if needed wouldn't affect backwards compatibility.

Or, there can be a default (overridable) max level of recursion, above which will cause a runtime error.

@gyreas
Copy link
Contributor

gyreas commented May 8, 2024

What does @casey think about a fmt() function that provides Python (or printf) style formatting for the meantime?

@casey
Copy link
Owner Author

casey commented May 15, 2024

I think the first easiest step is to parse f"blah {{foo}} blah", where the contents of an interpolation may only be a single identifier. This covers the most common case, but avoids the complexity of the fully recursive case.

@gyreas
Copy link
Contributor

gyreas commented May 16, 2024

In that case: no escaping, right?

@gyreas
Copy link
Contributor

gyreas commented May 20, 2024

@casey,
The branch you linked above is inexistent. I'll like to take this on, but I need a place to start from, otherwise it might take a while.

@casey
Copy link
Owner Author

casey commented May 20, 2024

The old branch is probably pretty out of date, so I'm not sure it will be much help.

@gyreas
Copy link
Contributor

gyreas commented May 21, 2024

Alright, anyhoo. I'll look into how #2055 works, and figure it out from there.

@gl-yziquel
Copy link
Contributor

Yeah. I'd love that addition too. Python strings f"{{}}" would have my preference.

@bukowa
Copy link
Contributor

bukowa commented Jul 8, 2024

PR started but author dropped out for personal reasons in 2021. Took me a while looking for this so linking for visibility

@gyreas
Copy link
Contributor

gyreas commented Jul 10, 2024

PR started but author dropped out for personal reasons in 2021. Took me a while looking for this so linking for visibility

Thanks. I think I can work with this.

A lot has gone into it too.

@liquidaty
Copy link

liquidaty commented Dec 30, 2024

*** updated ***
sorry, just realized shell() is already there. updated accordingly

**

Isn't this (or at least, a significant subset of the use cases it is meant to address) already solved via shell()

e.g. continuing the example from above

export VERSION := `...` # what can I do here to execute version_cmd

becomes

export VERSION := shell(version_cmd)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.