Repository: mitsuhiko/minijinja
Pull request #606 (2024-10-24 11:01:30)

Add --template to cli and allow config defaults


Patch:
diff --git a/minijinja-cli/src/cli.rs b/minijinja-cli/src/cli.rs
index 0fbca1f..8baa452 100644
--- a/minijinja-cli/src/cli.rs
+++ b/minijinja-cli/src/cli.rs
@@ -145,6 +145,7 @@ fn create_env(
     config: &Config,
     cwd: PathBuf,
     template_name: &str,
+    template_source: Option<String>,
     stdin_used_for_data: bool,
 ) -> Result<Environment<'static>, Error> {
     let mut env = Environment::new();
@@ -195,6 +196,10 @@ fn create_env(
                 })?);
             }
             return Ok(stdin.clone());
+        } else if name == template_name {
+            if let Some(ref source) = template_source {
+                return Ok(Some(source.clone()));
+            }
         }
 
         let fs_name = Path::new(name);
@@ -374,7 +379,7 @@ pub fn execute() -> Result<i32, Error> {
         }
     }
 
-    let (base_ctx, stdin_used) = if let Some(data) = matches.get_one::<PathBuf>("data") {
+    let (base_ctx, stdin_used) = if let Some(data) = matches.get_one::<PathBuf>("data_file") {
         load_data(
             config.format(),
             data,
@@ -386,13 +391,25 @@ pub fn execute() -> Result<i32, Error> {
 
     let cwd = std::env::current_dir()?;
     let ctx = context!(..config.defines(), ..base_ctx);
-    let template_name = match matches.get_one::<String>("template").unwrap().as_str() {
-        STDIN_STDOUT => Cow::Borrowed(STDIN_STDOUT),
-        rel_name => Cow::Owned(cwd.join(rel_name).to_string_lossy().to_string()),
+
+    let (template_name, template_source) = match (
+        matches.get_one::<String>("template"),
+        matches
+            .get_one::<String>("template_file")
+            .map(|x| x.as_str()),
+    ) {
+        (None, Some(STDIN_STDOUT)) => (Cow::Borrowed(STDIN_STDOUT), None),
+        (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!(),
     };
+
     let mut output = Output::new(matches.get_one::<PathBuf>("output").unwrap())?;
 
-    let env = create_env(&config, cwd, &template_name, stdin_used)?;
+    let env = create_env(&config, cwd, &template_name, template_source, stdin_used)?;
     let mut exit_code = 0;
 
     if let Some(expr) = matches.get_one::<String>("expr") {
diff --git a/minijinja-cli/src/command.rs b/minijinja-cli/src/command.rs
index 9b63a09..dc03397 100644
--- a/minijinja-cli/src/command.rs
+++ b/minijinja-cli/src/command.rs
@@ -235,6 +235,12 @@ pub(super) fn make_command() -> Command {
                     \
                     [env var: MINIJINJA_ENV]")
                 .help_heading(BEHAVIOR),
+            arg!(-t --template <TEMPLATE_STRING> "Render a string template")
+                .long_help("\
+                    Renders a template from a string instead of the file given.\n\n\
+                    \
+                    This can be used as an alternative to the template file that is normally passed. \
+                    Note that this is different to --expr which evaluates expressions instead."),
             arg!(-E --expr <EXPR> "Evaluates an template expression")
                 .long_help("\
                     Evalues a template expression instead of rendering a template.\n\n\
@@ -276,7 +282,7 @@ pub(super) fn make_command() -> Command {
                     Starts the read-eval loop with the given input data.\n\n\
                     \
                     This allows basic experimentation of MiniJinja expressions with some input data.")
-                .conflicts_with_all(["expr", "template"])
+                .conflicts_with_all(["expr", "template", "template_file"])
                 .help_heading(ADVANCED),
             arg!(-o --output <FILENAME> "Path to the output file")
                 .long_help("\
@@ -309,14 +315,15 @@ pub(super) fn make_command() -> Command {
                 .action(ArgAction::HelpShort),
             arg!(--"long-help" "Print long help (extended, long explanation texts)")
                 .action(ArgAction::HelpLong),
-            arg!(template: [TEMPLATE] "Path to the input template")
+            arg!(template_file: [TEMPLATE_FILE] "Path to the input template")
                 .long_help("\
                     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_value("-"),
-            arg!(data: [DATA] "Path to the data file")
+                .default_value("-")
+                .conflicts_with("template"),
+            arg!(data_file: [DATA_FILE] "Path to the data file")
                 .long_help("\
                     Path to the data file in the given format.\n\n\
                     \
diff --git a/minijinja-cli/src/config.rs b/minijinja-cli/src/config.rs
index 24e3cc5..3f1c3bc 100644
--- a/minijinja-cli/src/config.rs
+++ b/minijinja-cli/src/config.rs
@@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize};
 
 /// Overrides specific syntax settings.
 #[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "kebab-case")]
+#[serde(rename_all = "kebab-case", default)]
 pub struct SyntaxElements {
     block_start: String,
     block_end: String,
@@ -40,7 +40,7 @@ impl Default for SyntaxElements {
 
 /// Holds in-memory config state for the execution.
 #[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "kebab-case")]
+#[serde(rename_all = "kebab-case", default)]
 pub struct Config {
     format: String,
     autoescape: String,


Test patch:
diff --git a/minijinja-cli/tests/snapshots/test_basic__long_help.snap b/minijinja-cli/tests/snapshots/test_basic__long_help.snap
index 8103282..deb06f5 100644
--- a/minijinja-cli/tests/snapshots/test_basic__long_help.snap
+++ b/minijinja-cli/tests/snapshots/test_basic__long_help.snap
@@ -32,17 +32,17 @@ Examples:
 
     minijinja-cli --strict --env hello.j2
 
-Usage: minijinja-cli [OPTIONS] [TEMPLATE] [DATA]
+Usage: minijinja-cli [OPTIONS] [TEMPLATE_FILE] [DATA_FILE]
 
 Arguments:
-  [TEMPLATE]
+  [TEMPLATE_FILE]
           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: -]
 
-  [DATA]
+  [DATA_FILE]
           Path to the data file in the given format.
           
           The data file is used to supply the context (variables) to the template. Various file
@@ -95,6 +95,12 @@ Options:
           -D is_active:=true  defines a boolean
           -D is_true          shortform to define true boolean
 
+  -t, --template <TEMPLATE_STRING>
+          Renders a template from a string instead of the file given.
+          
+          This can be used as an alternative to the template file that is normally passed. Note that
+          this is different to --expr which evaluates expressions instead.
+
   -o, --output <FILENAME>
           Path to the output file instead of stdout.
           
diff --git a/minijinja-cli/tests/snapshots/test_basic__short_help.snap b/minijinja-cli/tests/snapshots/test_basic__short_help.snap
index a207bfb..e98209a 100644
--- a/minijinja-cli/tests/snapshots/test_basic__short_help.snap
+++ b/minijinja-cli/tests/snapshots/test_basic__short_help.snap
@@ -12,23 +12,24 @@ minijinja-cli is a command line tool to render or evaluate jinja2 templates.
 
 Pass a template and optionally a file with template variables to render it to stdout.
 
-Usage: minijinja-cli [OPTIONS] [TEMPLATE] [DATA]
+Usage: minijinja-cli [OPTIONS] [TEMPLATE_FILE] [DATA_FILE]
 
 Arguments:
-  [TEMPLATE]  Path to the input template [default: -]
-  [DATA]      Path to the data file
+  [TEMPLATE_FILE]  Path to the input template [default: -]
+  [DATA_FILE]      Path to the data file
 
 Options:
-      --config-file <PATH>  Alternative path to the config file.
-  -f, --format <FORMAT>     The format of the input data [possible values: auto, json, querystring,
-                            yaml, toml, cbor]
-  -D, --define <EXPR>       Defines an input variable (key=value / key:=json_value)
-  -o, --output <FILENAME>   Path to the output file [default: -]
-      --select <SELECTOR>   Select a subset of the input data
-      --print-config        Print out the loaded config
-  -h, --help                Print short help (short texts)
-      --long-help           Print long help (extended, long explanation texts)
-  -V, --version             Print version
+      --config-file <PATH>          Alternative path to the config file.
+  -f, --format <FORMAT>             The format of the input data [possible values: auto, json,
+                                    querystring, yaml, toml, cbor]
+  -D, --define <EXPR>               Defines an input variable (key=value / key:=json_value)
+  -t, --template <TEMPLATE_STRING>  Render a string template
+  -o, --output <FILENAME>           Path to the output file [default: -]
+      --select <SELECTOR>           Select a subset of the input data
+      --print-config                Print out the loaded config
+  -h, --help                        Print short help (short texts)
+      --long-help                   Print long help (extended, long explanation texts)
+  -V, --version                     Print version
 
 Template Behavior:
   -a, --autoescape <MODE>  Reconfigures autoescape behavior [possible values: auto, html, json,
diff --git a/minijinja-cli/tests/test_basic.rs b/minijinja-cli/tests/test_basic.rs
index eb03169..a986956 100644
--- a/minijinja-cli/tests/test_basic.rs
+++ b/minijinja-cli/tests/test_basic.rs
@@ -494,6 +494,108 @@ fn test_line_statement() {
     "###);
 }
 
+#[test]
+#[allow(clippy::suspicious_command_arg_space)]
+fn test_template_string() {
+    assert_cmd_snapshot!(
+        cli()
+            .arg("-tHello {{ name }}")
+            .arg("-Dname=Peter")
+            .arg("--no-newline"),
+        @r###"
+    success: true
+    exit_code: 0
+    ----- stdout -----
+    Hello Peter
+    ----- stderr -----
+    "###);
+}
+
+#[test]
+fn test_print_config_fully_loaded() {
+    assert_cmd_snapshot!(
+        cli()
+            .arg("--strict")
+            .arg("--trim-blocks")
+            .arg("-Dvar1=value1")
+            .arg("-Dvar2=value2")
+            .arg("-Dvar3:=42")
+            .arg("-Dvar4:=true")
+            .arg("-Dvar5:=[1,2,true]")
+            .arg("--print-config"),
+        @r###"
+    success: true
+    exit_code: 0
+    ----- stdout -----
+    format = "auto"
+    autoescape = "auto"
+    include = true
+    newline = true
+    trim-blocks = true
+    lstrip-blocks = false
+    py-compat = false
+    env = false
+    strict = true
+    safe-paths = []
+    expr-out = "print"
+    fuel = 0
+
+    [syntax]
+    block-start = "{%"
+    block-end = "%}"
+    variable-start = "{{"
+    variable-end = "}}"
+    comment-start = "{#"
+    comment-end = "#}"
+    line-statement-prefix = ""
+    line-comment-prefix = ""
+
+    [defines]
+    var1 = "value1"
+    var2 = "value2"
+    var3 = 42
+    var4 = true
+    var5 = [
+        1,
+        2,
+        true,
+    ]
+
+
+    ----- stderr -----
+    "###);
+}
+
+#[test]
+fn test_load_config() {
+    let config = file_with_contents_and_ext(
+        r#"
+    [defines]
+    greeting = "Hello"
+    punctuation = "!"
+    "#,
+        ".toml",
+    );
+
+    let input = file_with_contents_and_ext(r#"{"name": "World"}"#, ".json");
+
+    assert_cmd_snapshot!(
+        cli()
+            .arg("--config-file")
+            .arg(config.path())
+            .arg("-")
+            .arg(input.path())
+            .pass_stdin("{{ greeting }} {{ name }}{{ punctuation }}"),
+        @r###"
+    success: true
+    exit_code: 0
+    ----- stdout -----
+    Hello World!
+
+    ----- stderr -----
+    "###);
+}
+
 #[test]
 #[cfg(all(
     feature = "cbor",