casey / just

🤖 Just a command runner
https://just.systems
Creative Commons Zero v1.0 Universal
17.59k stars 399 forks source link

Port some functions from make #2012

Open gyreas opened 3 weeks ago

gyreas commented 3 weeks ago

This PR adds some useful functions found in make.

Please read the corresponding commit's message for how each works

To be added

laniakea64 commented 3 weeks ago

shell() -

This would be awesome, thanks! Would provide a solution for https://github.com/casey/just/issues/11#issuecomment-2015584186 :+1:

call() - this might require user-defined functions

Why would this require user-defined functions? Even without that, seems like call() would be a helpful tool.

wildcard()

Already achievable as

foo := `echo *.c`

addprefix()

addsuffix()

basename()

These seem easily implemented with replace_regex(), and doing it this way is more flexible. Using the examples from make documentation -

addprefix := replace_regex('foo bar', '(^|\s+)(\S+)', '${1}src/${2}')
addsuffix := replace_regex('foo bar', '(^|\s+)(\S+)', '${1}${2}.c')
basename := replace_regex('src/foo.c src-1.0/bar hacks', '(\S+)\.[^/ ]*(\s+|$)', '${1}${2}')
gyreas commented 3 weeks ago

Why would this require user-defined functions? Even without that, seems like call() would be a helpful tool.

My bad. I phrased it wrongly; I guess the proper phrasing would be: "call() is much useful with user-defined functions". I'll rephrase the description.

Already achievable as

foo := `echo *.c`

The point of these functions is to be mixed and matched. So, while echo *.c and its variations covers those cases for wildcard(), more complex mixing between wildcard() and the others might not be so easily covered. For example, from make documentation $(wildcard $(addsuffix suf,$(subst :, ,$(PATH)))) | v wildcard(addsuffix("suf", replace_all(":", PATH)))

addprefix := replace_regex('foo bar', '(^|\s+)(\S+)', '${1}src/${2}')
addsuffix := replace_regex('foo bar', '(^|\s+)(\S+)', '${1}${2}.c')
basename := replace_regex('src/foo.c src-1.0/bar hacks', '(\S+)\.[^/ ]*(\s+|$)', '${1}${2}')

It's certainly much readable to simply use addprefix(), addsuffix(), basename(), right?

Thanks for the feedback.

laniakea64 commented 3 weeks ago

The point of these functions is to be mixed and matched. So, while echo *.c and its variations covers those cases for wildcard(), more complex mixing between wildcard() and the others might not be so easily covered.

With shell() implemented, it could be done something like

shell('echo $@', addsuffix("/suf*", replace(env_var('PATH'), ":", ' ')))
gyreas commented 2 weeks ago

With shell() implemented, it could be done something like

shell('echo $@', addsuffix("/suf*", replace(env_var('PATH'), ":", ' ')))

I guess.

What do you think about using backslash for escaping? (Does just support raw strings?)

laniakea64 commented 2 weeks ago

What do you think about using backslash for escaping?

escaping what?

(Does just support raw strings?)

Yes - https://just.systems/man/en/chapter_29.html

gyreas commented 2 weeks ago

escaping what?

Placeholder escaping in shell(). I'm using $\d+ for argument substitution, and I'll naturally need to deal with escaping of $.

Also, is $\d+ preferred over ${\d+} ? (I prefer the former).

laniakea64 commented 2 weeks ago

Placeholder escaping in shell(). I'm using $\d+ for argument substitution, and I'll naturally need to deal with escaping of $.

I think it would be best to leave that to the shell and just pass in the arguments as command-line arguments. In just the shell could be set to anything, even something that isn't nominally a "shell" e.g. a Python interpreter.

So for example, with bash -

set shell := ['bash', '-uc']

# in bash the first argument is $0
# provide the justfile as dummy argument so that desired arguments are all in $@
foo := shell('ls -la "$@"', justfile(), 'actual_first_arg')

would run bash -uc 'ls -la "$@"' /path/to/justfile 'actual_first_arg'

With Python -

set shell := ['python3', '-c']

# argument 0 is the `-c` flag from above
foo := shell('''
  import sys
  print(sys.argv[1:])
''', 'argument 1', 'argument 2')

would run

python3 -c 'import sys
print(sys.argv[1:])
' 'argument 1' 'argument 2'
laniakea64 commented 2 weeks ago

Realizing that even with shell() using command-line to pass arguments, your question applies to call().

There is precedent for escaping $ and related in Replacement string syntax of replace_regex(), where escaped $ is $$.

Also, is $\d+ preferred over ${\d+} ? (I prefer the former).

Usually yes. ${\d+} is secondary alternative form for disambiguation - for example, writing $1 immediately followed by a literal 2 is ${1}2 to distinguish it from twelfth argument $12.

gyreas commented 2 weeks ago
set shell := ['python3', '-c']

# argument 0 is the `-c` flag from above
foo := shell('''
  import sys
  print(sys.argv[1:])
''', 'argument 1', 'argument 2')

This...

python3 -c 'import sys
print(sys.argv[1:])
' 'argument 1' 'argument 2'

... and this expose something I haven't thought of. I'll probably go this route since it's similar to how ` ` works. That means shell() is a convenient frontend to ` `, no?

I think it would be best to leave that to the shell and just pass in the arguments as command-line arguments. In just the shell could be set to anything, even something that isn't nominally a "shell" e.g. a Python interpreter.

Yeah. I'll make the necessary changes and push that.

gyreas commented 2 weeks ago
set shell := ['python3', '-c']

# argument 0 is the `-c` flag from above
foo := shell('''
  import sys
  print(sys.argv[1:])
''', 'argument 1', 'argument 2')

This...

python3 -c 'import sys
print(sys.argv[1:])
' 'argument 1' 'argument 2'

... and this expose something I haven't thought of. I'll probably go this route since it's similar to how ` ` works. That means shell() is a convenient frontend to ` `, no?

I think it would be best to leave that to the shell and just pass in the arguments as command-line arguments. In just the shell could be set to anything, even something that isn't nominally a "shell" e.g. a Python interpreter.

Yeah. I'll make the necessary changes and push that.

laniakea64 commented 2 weeks ago

That means shell() is a convenient frontend to ` `, no?

It's much better than that:

gyreas commented 2 weeks ago

Alright.

I think we're in sync.

PS: I think string interpolation can be done via a function fe fmt() that uses Rust/Python's formatting scheme. Maybe I should open a discussion for this (or just chime in to #11)?

laniakea64 commented 2 weeks ago

PS: I think string interpolation can be done via a function fe fmt() that uses Rust/Python's formatting scheme. Maybe I should open a discussion for this (or just chime in to #11)?

I don't know which option would be best. In your place I would probably start by replying to https://github.com/casey/just/issues/11#issuecomment-1506818296

gyreas commented 6 days ago
  • .trim() // as per Make
  • .lines()
  • .collect::<Vec<&str>>()
  • .join(" "); // as per Make

Please don't force this problematic make-ism. just does not need strict make compatibility, and an individual justfile can easily opt into make's behavior where desired by replace(trim(...), "\n", ' ').

Fair enough. I only did that since it's support to be port, I'm just trying to replicate that.

Saed