Repository: mitsuhiko/minijinja (language: rust)

Issue:
feat(cli): ability to pass data on stdin while using template source passed by parameter
2.4's new `--template` flag is great, particularly for Nushellers for whom process substitution isn't available 😅

When the `--template <TEMPLATE_STRING>` is used, potentially the next file parameter could be assumed to be the `DATA_FILE`?

```bash
$ minijinja-cli -t 'Hi {{ name }}' -D name=@mitsuhiko
Hi @mitsuhiko

# unable to pass the data as stdin (or other file)?
$ jo name='\@mitsuhiko' | minijinja-cli -f json -t 'Hi {{ name }}' -
error: the argument '--template <TEMPLATE_STRING>' cannot be used with '[TEMPLATE_FILE]'

Usage: minijinja-cli --format <FORMAT> --template <TEMPLATE_STRING> [TEMPLATE_FILE] [DATA_FILE]

For more information, try '--help'.
```

Thank you for everything 🙏


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cedbc6e3..a88e198b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,8 @@ All notable changes to MiniJinja are documented here.
 - Reversing bytes and convergint them implicitly to strings will now work
   more consistently.  #619
 - Added type hints for the Python binding and relaxed maturin constraint.  #590
+- `minijinja-cli` now allows the template name to be set to an empty
+  string when `--template` is used, to allow suppliying a data file.  #624
 
 ## 2.4.0
 
diff --git a/minijinja-cli/src/cli.rs b/minijinja-cli/src/cli.rs
index c4825ace..c24e74fa 100644
--- a/minijinja-cli/src/cli.rs
+++ b/minijinja-cli/src/cli.rs
@@ -409,12 +409,13 @@ pub fn execute() -> Result<i32, Error> {
             .map(|x| x.as_str()),
     ) {
         (None, Some(STDIN_STDOUT)) => (Cow::Borrowed(STDIN_STDOUT), None),
+        (None, Some("")) => bail!("Empty template names are only valid with --template."),
         (None, Some(rel_name)) => (
             Cow::Owned(cwd.join(rel_name).to_string_lossy().to_string()),
             None,
         ),
-        (Some(source), Some(STDIN_STDOUT)) => (Cow::Borrowed("<string>"), Some(source.clone())),
-        _ => unreachable!(),
+        (Some(source), None | Some("")) => (Cow::Borrowed("<string>"), Some(source.clone())),
+        _ => bail!("When --template is used, a template cannot be passed as argument (only an empty argument is allowed)."),
     };
 
     let mut output = Output::new(matches.get_one::<PathBuf>("output").unwrap())?;
diff --git a/minijinja-cli/src/command.rs b/minijinja-cli/src/command.rs
index 585c5bb5..1a9c6f01 100644
--- a/minijinja-cli/src/command.rs
+++ b/minijinja-cli/src/command.rs
@@ -3,6 +3,7 @@
 /// application and by build.rs to generate shell completions.
 use std::path::PathBuf;
 
+use clap::builder::ArgPredicate;
 use clap::{arg, command, value_parser, ArgAction, Command};
 
 const ADVANCED: &str = "Advanced";
@@ -324,9 +325,12 @@ pub(super) fn make_command() -> Command {
                     This is the path to the input template in MiniJinja/Jinja2 syntax.  \
                     If not provided this defaults to '-' which means the template is \
                     loaded from stdin.  When the format is set to 'auto' which is the \
-                    default, the extension of the filename is used to detect the format.")
+                    default, the extension of the filename is used to detect the format.\n\n\
+                    \
+                    This argument can be set to an empty string when --template is provided \
+                    to allow a data file to be supplied.")
                 .default_value("-")
-                .conflicts_with("template"),
+                .default_value_if("template", ArgPredicate::IsPresent, None),
             arg!(data_file: [DATA_FILE] "Path to the data file")
                 .long_help("\
                     Path to the data file in the given format.\n\n\


Test patch:
diff --git a/minijinja-cli/tests/test_basic.rs b/minijinja-cli/tests/test_basic.rs
index 4ddc654a..48c97646 100644
--- a/minijinja-cli/tests/test_basic.rs
+++ b/minijinja-cli/tests/test_basic.rs
@@ -656,6 +656,63 @@ fn test_template_string() {
     "###);
 }
 
+#[test]
+#[allow(clippy::suspicious_command_arg_space)]
+fn test_empty_template_name_with_string_template() {
+    let input = file_with_contents_and_ext(r#"{"name": "Peter"}"#, ".json");
+    assert_cmd_snapshot!(
+        cli()
+            .arg("-tHello {{ name }}")
+            .arg("")
+            .arg(input.path())
+            .arg("--no-newline"),
+        @r###"
+    success: true
+    exit_code: 0
+    ----- stdout -----
+    Hello Peter
+    ----- stderr -----
+    "###);
+}
+
+#[test]
+#[allow(clippy::suspicious_command_arg_space)]
+fn test_template_name_with_string_template_fails() {
+    let input = file_with_contents_and_ext(r#"{"name": "Peter"}"#, ".json");
+    assert_cmd_snapshot!(
+        cli()
+            .arg("-tHello {{ name }}")
+            .arg("invalid.tmpl")
+            .arg(input.path())
+            .arg("--no-newline"),
+        @r###"
+    success: false
+    exit_code: 1
+    ----- stdout -----
+
+    ----- stderr -----
+    error: When --template is used, a template cannot be passed as argument (only an empty argument is allowed).
+    "###);
+}
+
+#[test]
+fn test_empty_template_name_errors() {
+    let input = file_with_contents_and_ext(r#"{"name": "Peter"}"#, ".json");
+    assert_cmd_snapshot!(
+        cli()
+            .arg("")
+            .arg(input.path())
+            .arg("--no-newline"),
+        @r###"
+    success: false
+    exit_code: 1
+    ----- stdout -----
+
+    ----- stderr -----
+    error: Empty template names are only valid with --template.
+    "###);
+}
+
 #[test]
 fn test_print_config_fully_loaded() {
     assert_cmd_snapshot!(