Repository: casey/just (rust)

Issue:
`set positionnal-arguments` append target at the end of the command on Windows but not on Linux
Hi !
The title says it all. Here is a justfile to reproduce the issue.

```just
# Enable positional args
set positional-arguments

set windows-shell := ["powershell.exe", "-NoLogo", "-Command"]
set shell := ["bash", "-c"]

target:
  echo hey
```

On Linux (my wsl) with just version 1.25.2, I get the following result

```
echo hey
hey
```

and on Windows Powershell with just version 1.28.0, I get the following result:

```
echo hey
hey
target
```

Removing `set positional-arguments` make it behave the same way in both Windows and Linux.

Did I understood something wrong ?
Thanks for any help !

Pull request:
Add [positional-arguments] attribute
Fixes #2146.

Patch:
diff --git a/README.md b/README.md
index 040639f0..27e0ba56 100644
--- a/README.md
+++ b/README.md
@@ -996,6 +996,20 @@ $ just test foo "bar baz"
 - bar baz
 ```
 
+Positional arguments may also be turned on on a per-recipe basis with the
+`[positional-arguments]` attribute<sup>master</sup>:
+
+```just
+[positional-arguments]
+@foo bar:
+  echo $0
+  echo $1
+```
+
+Note that PowerShell does not handle positional arguments in the same way as
+other shells, so turning on positional arguments will likely break recipes that
+use PowerShell.
+
 #### Shell
 
 The `shell` setting controls the command used to invoke recipe lines and
@@ -1686,6 +1700,7 @@ Recipes may be annotated with attributes that change their behavior.
 | `[no-cd]`<sup>1.9.0</sup> | Don't change directory before executing recipe. |
 | `[no-exit-message]`<sup>1.7.0</sup> | Don't print an error message if recipe fails. |
 | `[no-quiet]`<sup>1.23.0</sup> | Override globally quiet recipes and always echo out the recipe. |
+| `[positional-arguments]`<sup>master</sup> | Turn on [positional arguments](#positional-arguments) for this recipe. |
 | `[private]`<sup>1.10.0</sup> | See [Private Recipes](#private-recipes). |
 | `[unix]`<sup>1.8.0</sup> | Enable recipe on Unixes. (Includes MacOS). |
 | `[windows]`<sup>1.8.0</sup> | Enable recipe on Windows. |
@@ -3362,9 +3377,9 @@ foo argument:
   touch "$1"
 ```
 
-This defeats `just`'s ability to catch typos, for example if you type `$2`, but
-works for all possible values of `argument`, including those with double
-quotes.
+This defeats `just`'s ability to catch typos, for example if you type `$2`
+instead of `$1`, but works for all possible values of `argument`, including
+those with double quotes.
 
 #### Exported Arguments
 
diff --git a/src/attribute.rs b/src/attribute.rs
index f9c5bfcd..5e83891f 100644
--- a/src/attribute.rs
+++ b/src/attribute.rs
@@ -17,6 +17,7 @@ pub(crate) enum Attribute<'src> {
   NoCd,
   NoExitMessage,
   NoQuiet,
+  PositionalArguments,
   Private,
   Unix,
   Windows,
@@ -32,6 +33,7 @@ impl AttributeDiscriminant {
       | Self::NoCd
       | Self::NoExitMessage
       | Self::NoQuiet
+      | Self::PositionalArguments
       | Self::Private
       | Self::Unix
       | Self::Windows => 0..=0,
@@ -78,6 +80,7 @@ impl<'src> Attribute<'src> {
       NoCd => Self::NoCd,
       NoExitMessage => Self::NoExitMessage,
       NoQuiet => Self::NoQuiet,
+      PositionalArguments => Self::PositionalArguments,
       Private => Self::Private,
       Unix => Self::Unix,
       Windows => Self::Windows,
@@ -98,6 +101,7 @@ impl<'src> Attribute<'src> {
       | Self::NoCd
       | Self::NoExitMessage
       | Self::NoQuiet
+      | Self::PositionalArguments
       | Self::Private
       | Self::Unix
       | Self::Windows => None,
diff --git a/src/recipe.rs b/src/recipe.rs
index 13b3bb99..e27cc2e0 100644
--- a/src/recipe.rs
+++ b/src/recipe.rs
@@ -106,6 +106,10 @@ impl<'src, D> Recipe<'src, D> {
     !self.private && !self.attributes.contains(&Attribute::Private)
   }
 
+  pub(crate) fn takes_positional_arguments(&self, settings: &Settings) -> bool {
+    settings.positional_arguments || self.attributes.contains(&Attribute::PositionalArguments)
+  }
+
   pub(crate) fn change_directory(&self) -> bool {
     !self.attributes.contains(&Attribute::NoCd)
   }
@@ -263,7 +267,7 @@ impl<'src, D> Recipe<'src, D> {
 
       cmd.arg(command);
 
-      if context.settings.positional_arguments {
+      if self.takes_positional_arguments(context.settings) {
         cmd.arg(self.name.lexeme());
         cmd.args(positional);
       }
@@ -415,7 +419,7 @@ impl<'src, D> Recipe<'src, D> {
           output_error,
         })?;
 
-    if context.settings.positional_arguments {
+    if self.takes_positional_arguments(context.settings) {
       command.args(positional);
     }
 


Test patch:
diff --git a/tests/positional_arguments.rs b/tests/positional_arguments.rs
index f412f0c3..86e4da19 100644
--- a/tests/positional_arguments.rs
+++ b/tests/positional_arguments.rs
@@ -24,6 +24,31 @@ test! {
   "#,
 }
 
+test! {
+  name: linewise_with_attribute,
+  justfile: r#"
+    [positional-arguments]
+    foo bar baz:
+      echo $0
+      echo $1
+      echo $2
+      echo "$@"
+  "#,
+  args:   ("foo", "hello", "goodbye"),
+  stdout: "
+    foo
+    hello
+    goodbye
+    hello goodbye
+  ",
+  stderr: r#"
+    echo $0
+    echo $1
+    echo $2
+    echo "$@"
+  "#,
+}
+
 test! {
   name: variadic_linewise,
   justfile: r#"
@@ -51,6 +76,18 @@ test! {
   stdout: "hello\n",
 }
 
+test! {
+  name: shebang_with_attribute,
+  justfile: "
+    [positional-arguments]
+    foo bar:
+      #!/bin/sh
+      echo $1
+  ",
+  args:   ("foo", "hello"),
+  stdout: "hello\n",
+}
+
 test! {
   name: variadic_shebang,
   justfile: r#"