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

Arguments array - should the program name be included? #2123

Closed
rsp opened this issue Apr 15, 2019 · 30 comments · Fixed by #3628
Closed

Arguments array - should the program name be included? #2123

rsp opened this issue Apr 15, 2019 · 30 comments · Fixed by #3628

Comments

@rsp
Copy link
Contributor

rsp commented Apr 15, 2019

Is there a reason that the program name is included in Deno.args?

In comparison to Node (that includes the node binary path in process.argv) Deno removes the deno binary path from the Deno.args and puts it inDeno.execPath. The TS/JS program name is available in location.pathname but unlike the Deno.execPath it is still left in the Deno.args. Is there a reason for that or is it just a coincidence of not having location.pathname in the past?

If we want to be able to write short one-liners with deno eval then it would be useful to use .map() and forEach() on Deno.args directly instead of Deno.args.slice(1).forEach(...) - is it still possible to change the current Deno.args to include only the arguments to the program or is it too late for that?

Possible options

When a script asks for its arguments, there are few possible answers to give.
Let's say we run hypothetically deno run -RWX script.ts arg (using syntax from #2081)
What should be in Deno.args?

  1. everything that is in argv[] of the process
    • it would be: ["deno", "run", "-RWX", "script.ts", "arg"]
  2. like (1) but with the interpreter arguments removed
    • Node does that
    • it would be: ["deno", "script.ts", "arg"]
  3. a script name and its arguments
    • Python does that
    • it would be: ["script.ts", "arg"]
  4. just the actual arguments
    • Perl, Ruby, sh, bash and other shells do that
    • it would be: ["arg"]

I would argue that from the point of view of a script the most important is an array or its actual arguments (["arg"] in the examples above) like in Perl, Ruby and shells and it's convenient that we don't need to skip the first one like in Python and Deno or the first two like in Node, and while it's sometimes useful to know the location of the script and the interpreter, they don't need to be in the same array because they are not really arguments (to the program in question, only to the compiler).

Additionally, the paths to the runtime and the script if present can be left as they were in the actual argv[] of the process, or can they be expanded to the full path (as is done by Node, but not by $0 in Bash or Perl - they leave it as it was in real invocation) and not by Deno (currently) in Deno.args[0] (unlike location.pathname).

Examples in other runtimes

Examples of argument arrays in Deno and other runtimes:
(note that only Node includes the path to node binary)

$ node <(echo 'console.log(process.argv)') arg
[ '/Users/rsp/opt/node-v10.6.0-darwin-x64/bin/node',
  '/dev/fd/63',
  'arg' ]

python <(echo 'import sys; print sys.argv') arg
['/dev/fd/63', 'arg']

$ ruby <(echo 'print ARGV') arg
["arg"]

$ perl <(echo 'print @ARGV') arg
arg

$ bash <(echo 'echo $@') arg
arg

$ deno <(echo 'console.log(Deno.args)') 1 2 3
[ "/dev/fd/63", "1", "2", "3" ]

Examples of program names in Deno and other runtimes:
(note that only Node expands the path)

$ echo 'console.log(Deno.args[0])' > x.ts; deno x.ts
x.ts

$ echo 'console.log(process.argv[1])' > x.js; node x.js
/Users/rsp/talks/deno/git/ntd/x.js

$ echo 'echo $0' > x.sh; bash x.sh
x.sh

$ echo 'print $0' > x.pl; perl x.pl
x.pl

$ echo 'import sys; print sys.argv[0]' > x.py; python x.py
x.py

$ echo 'print $0' > x.rb; ruby x.rb
x.rb
@bartlomieju
Copy link
Member

@rsp just for clarification with recent changes to CLI Deno behaves like example no. 2

deno run -RWX script.ts arg
// Deno.args
["deno", "script.ts", "arg"]

@rsp
Copy link
Contributor Author

rsp commented Apr 16, 2019

@bartlomieju so it will be more like Node which would mean skipping two elements to get the actual program arguments. I'm wondering if that is a good use of Deno.args if Deno.args[0] and Deno.args[1] are elements that are not arguments to the program, and that are already available in Deno.execPath and location.pathname. I can understand the rationale to put the program name in argv[] to have a single array on such a low level because it simplifies the main() but even shells put the script name in $0 and keep only the actual arguments in $@. I don't see any use case of iterating over the interpreter name, script name and script arguments in one loop. Is there a reason for that?

(Update: I would understand the logic of having full ["deno", "run", "-RWX", "script.ts", "arg"] somewhere because at least that would be the real invocation of the process but ["deno", "script.ts", "arg"] is neither full nor convenient.)

@bartlomieju
Copy link
Member

bartlomieju commented Apr 16, 2019

@rsp agreed. Deno.args[0] is hard coded on Rust side so it's always "deno". Deno.args[1] is script name that is optional (if you start REPL with deno). Maybe it's worth considering moving it to Deno.scriptName/Deno.entryPoint? Regarding passing through all args (including interpreter flags) I don't really like this idea, user scripts shouldn't be aware of them (there's already permissions API available via Deno.permissions())

@rsp
Copy link
Contributor Author

rsp commented Apr 16, 2019

@bartlomieju I agree that scripts shouldn't care about the interpreter flags, it was just an example for completeness sake. I think that ideally Deno.args should contain only args to the script, so e.g. for deno run -RWX script.ts a b c we would have:

  • Deno.args is ["a", "b", "c"]
  • Deno.name/Deno.denoName/runtimeName/binName/execName/etc. - what was actually used to run the interpreter (e.g. deno or ./deno or ../deno), currently unavailable
  • Deno.path/Deno.denoPath/runtimePath/binPath/execPath/etc. - what is the actual path of the interpretter (e.g. /usr/bin/deno or /home/rsp/bin/deno), currently in Deno.execPath
  • Deno.scriptName/Deno.programName/etc. - what was actually used in the command line, like $0 in shell or Perl or Ruby (e.g. ./script.ts or ../../script.ts), currently in Deno.args[0] or Deno.args[1] (if it still keeps the original name from the command line)
  • Deno.scriptPath/Deno.programPath/etc. for consistency or just keep the location.pathname as an exception for this one - currently it is in location.pathname

For example for the command: ./deno run -RWX dir/script.ts a b c run in directory /home/x/tests it would be good to have access separately to:

  • ["a", "b", "c"]
  • "./deno"
  • "/home/x/tests/deno"
  • "dir/script.ts"
  • "/home/x/tests/dir/script.ts"

Plus it would be nice to have some consistency in the names because those will probably never change.

(For the permissions API I agree that scripts should use Deno.permissions() instead of the params, especially when Deno.revokePermission() might have been used so there may be less permissions than what was used to start the program.)

@ry
Copy link
Member

ry commented Sep 6, 2019

I can't say I have a strong opinion on this, but we should figure it out before 1.0... I'm adding this issue to the blockers.

@ry ry mentioned this issue Sep 6, 2019
43 tasks
@Fenzland
Copy link
Contributor

Maybe the same as sh and bash is just okay.

test.sh

#!/bin/sh
echo $0 $@
> sh test.sh -v
test.sh -v
> ./test.sh -v
test.sh -v
> sh -v test.sh
#!/bin/sh
echo $0 $@
test.sh
> sh -a <(cat test.sh) -b
/dev/fd/61 -b

Shell name and shell options will be hidden, index 0 always for the script file, follows the arguments for script.

@NotWearingPants
Copy link

I agree with @rsp about the args and program path, but I vote against having access to the deno name/path.

Programs shouldn't care about their interpreter, and should not be aware of it.

Also, what happens when the script is ran directly with a shebang line?
./app.ts
Will the runtimePath be empty?

@rsp
Copy link
Contributor Author

rsp commented Nov 18, 2019

@NotWearingPants actually when the script is run directly with a shebang line (as ./app.ts and I hope also ./app with no extension, as was discussed in issues #929 and #1226) it is run as <interpreter from the shebang line> ./app.ts so there is no difference (other than the script having to have both R and X permissions for the users who run it, plus some quirks related to the number of arguments as I mentioned here), I described it in more detail in my answer on Quora about it, so in this example the runtimePath would never be empty.

I also tend to agree that what sh/bash/ruby/perl is doing is the most reasonable way, as also @Fenzland commented above. Mainly because I rarely treat the script name as one of the arguments so if the invocation:

$ deno run script.ts x y

would not give us the arguments as ['x', 'y'] array directly but as a part of a bigger array, like ['script.ts', 'x', 'y'] or ['deno', 'run', 'script.ts', 'x', 'y'] then pretty much every access to arguments will be done with slice() because most of the use cases need either the name of the script or runtime or the actual arguments passed to our script.

Update: As @bartlomieju showed in April, Deno worked like Node:

$ deno run --allow-read script.ts arg1 arg2
["deno", "script.ts", "arg1", "arg2"]

Now it seems that it works like Python:

$ deno run --allow-read script.ts arg1 arg2
[ "script.ts", "arg1", "arg2" ]

I believe it would be most useful to work like sh, Bash, Perl and Ruby:

$ deno run --allow-read script.ts arg1 arg2
[ "arg1", "arg2" ]

because I don't see any value in confusing the script name with one of its arguments (the only argument would be a consistency with C but this is higher level language and user experience is more important than reusing a single array to serve two purposes).

I am talking about the array returned by Deno.args.

I wanted to clarify as this is listed as one of the blockers for Major features necessary for 1.0 #2473.

@rsp
Copy link
Contributor Author

rsp commented Nov 18, 2019

By the way, some edge cases:

Eval has the full script in Deno.args[0]:

$ deno eval 'console.log(Deno.args);'
[ "console.log(Deno.args);" ]

REPL has nothing at Deno.args[0]:

$ deno
> console.log(Deno.args);
[]
undefined

I couldn't run REPL with args to see what's in Deno.args:

$ deno -- arg1 arg2
Cannot resolve module "file:///Users/rsp/deno/test/args/arg1"

I tried eval with arguments, take 1:

$ deno eval 'console.log(Deno.args);' arg1 arg2
error: Found argument 'arg1' which wasn't expected, or isn't valid in this context

Eval with arguments, take 2:

$ deno eval 'console.log(Deno.args);' -- arg1 arg2
error: Found argument 'arg1' which wasn't expected, or isn't valid in this context

Maybe the -- works as expected but I see no way to pass arguments to REPL (not that I think this is very useful) but what I'm concerned about is the empty Deno.args in REPL, while having script or script name as the first element in other cases.

I think having only arguments to our program in Deno.args (as opposed to have all arguments to our program program prefixed with some arguments to deno) would be both most useful and consistent with having [] in REPL.

(Edit: removed redundant examples)

@rsp
Copy link
Contributor Author

rsp commented Nov 18, 2019

Interesting values of location.pathname:

$ deno eval 'console.log(location.pathname);'
/Users/rsp/deno/test/args/console.log(location.pathname);

In REPL it throws:

$ deno
> console.log(location.pathname);
error: Uncaught TypeError: Cannot read property 'pathname' of undefined

@szhu
Copy link

szhu commented Dec 22, 2019

Just the actual arguments seems like the most reasonable approach given Deno's goals.

Including any more arguments (and especially hard-coding deno as arg 0) seems like it's generally being done for backwards compatibility, and the rationale behind doing so will become increasingly harder to explain to people as Deno (hopefully??) becomes some people's first CLI language.

The entire command line might be important to include in certain cases (i.e. for printing out a warning that a script is being run an incorrect way). But this should be an advanced feature, and what it contains will probably change as Deno evolves over time (for example, some --allow-* flags might be added or renamed), so it should be not the default way to get the actual arguments.

@sholladay
Copy link

I also want Deno.args to only include the arguments given to my program, not the arguments given to Deno itself, and certainly not including Deno's own name/path. Those other bits of info can be useful in some limited circumstances and should be available separately, but not included in Deno.args. We already have Deno.execPath() and others.

@axetroy
Copy link
Contributor

axetroy commented Dec 25, 2019

In some cases, it is necessary to obtain the complete command

So I think it is necessary to include

/usr/bin/env deno run example.ts

@nayeemrmn
Copy link
Collaborator

@axetroy Necessary, but maybe given through some op as @sholladay suggested. Not necessarily in Deno.args which this issue is about.

@sholladay
Copy link

There could be a new Deno.spawnArgs (name TBD) for those who need to create child processes and such with the same Deno executable and arguments. But this is a niche case compared to reading only the script's arguments.

@timonson
Copy link
Contributor

timonson commented Dec 27, 2019

Bash has the actual arguments as positional parameters but it actually has the script name in $0. I personally like [ /path/script.ts, arg1, arg2, arg3 ].

@szhu
Copy link

szhu commented Dec 29, 2019

Just wanted to summarize some of the viewpoints expressed on this thread so far. It looks like there are users who:

  • Want every single arg in Deno's argv
  • Want the script name/path arg and args to the script passed
  • Want just the args to the script passed

It doesn't really appear like there's a right or wrong here, as each of these has valid and common use cases. It's probably best to come up with an API that can support all of these three cases with as little confusion between them as possible. (For each use case, it should be clear which API to use.)

Would the following combination address everyone's needs?

  1. A way to access every single arg in Deno's argv
  2. A way to access either the script name. This would be a single string that would be either whatever was passed for the name/path arg or it would be the absolute path to the script
  3. A way to access just the args to the script

@szhu
Copy link

szhu commented Dec 29, 2019

Just another note on why I think Deno.args should be just the args to the script.

Deno will hopefully become a tool used by beginners and people who haven't used the command line before. But these people will probably be somewhat familiar with JavaScript functions and arguments.

Passing just the args to the script in Deno.args is by far the most similar thing to function args out of all the options considered here. Passing the script name before args is like having a JS function where the first argument is the function itself -- if you've never needed it, it's unexpected and hard to figure out why it's there.

Note that many of these users might not really care about the command-line environment at all. They don't care about what's in a program's real argv. All they care about is that they wrote a JavaScript function that runs in the browser, and they want to get it to work in a terminal too.

Deno should make it easy for users who don't care about the command line to write robust command-line programs. This should matter even for people who do care about the command line -- you want to be able to use code written by good JS developers without worrying that it'll break simply because they don't know how the command line works.

@timonson
Copy link
Contributor

Great points @szhu, I can see the advantages of having only the script arguments in Deno.args because simplification is very important. If there would be another way to access the script name, I would be all for it.

@ry
Copy link
Member

ry commented Jan 8, 2020

We've discussed this and we're going to change the API so that Deno.args includes just the script arguments, not the script, not the deno executable, and not deno args (like --reload).

@Alhadis
Copy link
Contributor

Alhadis commented Jan 12, 2020

So what's the procedure for users who do want/need the path of the originally-invoked script?

I assume it's not shelling out to ps(1) and grepping for a line containing Deno.pid, then manually extracting the actual command-line arguments and finding the first one that quacks like a pathname.

EDIT: Never mind, I saw the comment mentioning location.pathname. I suggest documenting this in the description of Deno.execPath or Deno.args, because it's a really confusing inconsistency.

@sholladay
Copy link

@Alhadis using window.location might be a bad idea until #3520 is resolved, otherwise your code might subdtly change or error at runtime in the near future.

I'm not sure what the proper solution is. Perhaps a new property on import.meta? Either way, good documentation here is sorely needed.

@Alhadis
Copy link
Contributor

Alhadis commented Jan 13, 2020

I'm not sure what the proper solution is. Perhaps a new property on import.meta?

import.meta should really only be used for storing (meta-)properties that vary between files. The path of the original script is constant, so it makes more sense to make it a read-only property of the Deno global:

Deno.execPath === "/usr/local/bin/deno";
Deno.scriptPath === "/home/Alhadis/test.mjs";

@hayd
Copy link
Contributor

hayd commented Jan 13, 2020

One breaking change could be to make .args a method and then it could take options e.g. include script, include deno flags, deno executable etc.


Deno.args() // current Deno.args
Deno.args({ includeScript: true }) // etc

@qngapparat
Copy link

qngapparat commented Jan 14, 2020

@hayd Not a fan. I think 'soft' design decisions like that compound as complexity.

@rsp
Copy link
Contributor Author

rsp commented Jan 14, 2020

Instead of a braking change with:

Deno.args({ includeScript: true })

we could can already do:

[location.pathname, ...Deno.args]

if we need an array like this.

@ry
Copy link
Member

ry commented Jan 14, 2020

I think @Alhadis's suggestion to add a Deno.scriptPath (maybe Deno.scriptUrl?) sounds reasonable.

#2553 (deno bundle doesn't work with import.meta) is related. If we added Deno.scriptPath we could move away from using import.meta.url for main detection.

@rsp
Copy link
Contributor Author

rsp commented Jan 14, 2020

@ry I wrote about Deno.scriptPath above here plus some other paths/names that could be useful.

Edit: +1 for Deno.scriptPath of course.

@hayd
Copy link
Contributor

hayd commented Jan 14, 2020

[location.pathname, ...Deno.args]

👍 We should document this in Deno.args, and how to splice in the permissions as well.

@hayd
Copy link
Contributor

hayd commented Jan 14, 2020

RE permissions/deno flags, this came up for me in wanting to query whether --lock was passed.

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

Successfully merging a pull request may close this issue.