just
just copied to clipboard
Allow interpolation inside of backticks and strings
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.
This does introduce the odd scenario where you can have interpolation inside a string inside an interpolation inside a string, etc, etc, etc.
Closing. If anyone wants this, comment and I'll reopen it!
Maybe thinking that we should do this. Reopening.
If this is supported, I'm inclined to do it with python's f-string syntax: f'some string with{{interpolation}}'
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!
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.
Is there any news on the subject?
edit: without this I'm forced to go back to make 😅
Is there any news on the subject?
This is a big feature, so time to implementation is unknown.
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
Could this crate be any helpful ?
MiniJinja -- Powerful but minimal dependency template engine for Rust
The template language isn't so much the hard part, it's the rest of the integration, so a crate wouldn't be a huge help.
For those who might be interested, I developed a simple task system based on the Ruby language.
I needed something effective, pretty simple to use and quick to develop for a project. It already proved itself to be quite powerful : https://github.com/pyrsmk/run
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)
i'm hoping for this addition.
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.
I'm also interested in this kind of functionality (edit: I'm interested in substitution in backticks in particular), but actually I don't care that this is provided using backticks: for my current use case, a function could be fine as well. Would something like an eval(string) function be simpler to implement? In that way, we could compose strings and use variables inside expressions:
world := "World!"
greet arg=world:
echo "Hello {{ eval("echo " + arg) }}"
I took a quick look at the code and if the implementation is performed trivially, one could implement a new eval function that basically replicates what is being done in run_backtick, although it seems that there is no access to Settings from the FunctionContext (and I didn't investigate if that's something that belongs in there).
Edit 2: I would also consider functions to substitute in strings. I think it's a simpler approach that would provide the feature in backward-compatible way and does not rely on the parser.
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.
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
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.
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.
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.
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.
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
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.
What does @casey think about a fmt() function that provides Python (or printf) style formatting for the meantime?
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.
In that case: no escaping, right?
@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.
The old branch is probably pretty out of date, so I'm not sure it will be much help.
Alright, anyhoo. I'll look into how #2055 works, and figure it out from there.
Yeah. I'd love that addition too. Python strings f"{{}}" would have my preference.