From 887b721566339fcd7c64eb809569b9f03e2c6300 Mon Sep 17 00:00:00 2001 From: "Vedo A." <113302287+vedoa@users.noreply.github.com> Date: Tue, 16 Dec 2025 11:40:07 +0100 Subject: [PATCH 1/7] Add support for derived variables #79 (#80) * Add support for derived variables #79 * Make prompt optional but required if derived not set to true. Add exmaple to github actions. * Update README * Add version * Proper format for date --------- Co-authored-by: vedoa <> --- .github/workflows/ci.yaml | 3 +- README.md | 15 +++- examples/derived/template.toml | 14 +++ examples/derived/{{package_path}}/Main.java | 7 ++ src/definition.rs | 97 ++++++++++++++++++++- src/main.rs | 15 +++- 6 files changed, 143 insertions(+), 8 deletions(-) create mode 100644 examples/derived/template.toml create mode 100644 examples/derived/{{package_path}}/Main.java diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e8f2032..7e7cf89 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -44,4 +44,5 @@ jobs: cargo run --features=cli -- examples/default-from-variable/ --no-input -o default cargo run --features=cli -- examples/slugify/ --no-input -o slugify cargo run --features=cli -- examples/super-basic/ --no-input -o super-basic - cargo run --features=cli -- examples/with-directory/ --no-input -o with-directory \ No newline at end of file + cargo run --features=cli -- examples/with-directory/ --no-input -o with-directory + cargo run --features=cli -- examples/derived/ --no-input -o derived \ No newline at end of file diff --git a/README.md b/README.md index 759418d..14d754c 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,11 @@ default = "my-project" prompt = "What is the name of this project?" validation = "^([a-zA-Z][a-zA-Z0-9_-]+)$" +[[variables]] +name = "slug" +default = "{{ project_name | slugify }}" +derived = true + [[variables]] name = "database" default = "postgres" @@ -204,10 +209,11 @@ A variable has the following required fields: - `name`: the name of the variable in Tera context - `default`: the default value for that question, `kickstart` uses that to deduce the type of that value (only string, bool and integer are currently supported). You can use previous variables in the default, eg `"{{ project_name | lower }}"` will replace `project_name` with the value of the variable. -- `prompt`: the text to display to the user +- `prompt`: the text to display to the user unless the variable is derived -And three more optional fields: +and four more optional fields: +- `derived`: set to `true` if the variable should not prompt the user and instead be computed from default - `choices`: a list of potential values, `kickstart` will make the user pick one - `only_if`: this question will only be asked if the variable `name` has the value `value` - `validation`: a Regex pattern to check when getting a string value @@ -230,6 +236,11 @@ You can use these like any other filter, e.g. `{{variable_name | camel_case}}`. ## Changelog +### 0.5.1 (2025-12-08) + +- New `derived = true` flag allows variables to be computed from default without prompting the user +- `prompt` remains required for non-derived variables, but is optional when `derived` is set to true + ### 0.5.0 (2024-12-13) - The `sub-dir` parameter has been renamed to `directory` in the CLI diff --git a/examples/derived/template.toml b/examples/derived/template.toml new file mode 100644 index 0000000..3d35fae --- /dev/null +++ b/examples/derived/template.toml @@ -0,0 +1,14 @@ +name = "Java domain" +description = "Dynamic folder structure" +kickstart_version = 1 + +[[variables]] +name = "package" +default = "my.domain.test" +prompt = "Enter your package name (dot-separated):" +validation = "^[a-z]+(\\.[a-z][a-z0-9_]*)*$" + +[[variables]] +name = "package_path" +default = "{{ package | replace(from='.', to='/')}}" +derived = true diff --git a/examples/derived/{{package_path}}/Main.java b/examples/derived/{{package_path}}/Main.java new file mode 100644 index 0000000..14d1670 --- /dev/null +++ b/examples/derived/{{package_path}}/Main.java @@ -0,0 +1,7 @@ +package {{package}}; + +public class Main { + public static void main(String[] args) { + System.out.println("Hello, World!"); + } +} diff --git a/src/definition.rs b/src/definition.rs index a788eae..8c6d8f5 100644 --- a/src/definition.rs +++ b/src/definition.rs @@ -35,13 +35,15 @@ pub struct Variable { /// A default value is required. It can be a Tera expression if it is a string. pub(crate) default: Value, /// The text asked to the user - pub prompt: String, + pub prompt: Option, /// Only for questions with choices pub choices: Option>, /// A regex pattern to validate the input. Only used where the value is meant to be a string. pub validation: Option, /// Only ask this variable if that condition is true pub only_if: Option, + /// Whether this variable is derived and should not be prompted + pub derived: Option, } /// A hook is a file that will get executed @@ -132,6 +134,22 @@ impl TemplateDefinition { } for var in &self.variables { + if var.prompt.is_none() && !var.derived.unwrap_or(false) { + errs.push(format!( + "Variable `{}` must have either a prompt or be marked as derived", + var.name + )); + } + + if let Some(ref prompt) = var.prompt { + if prompt.trim().is_empty() { + errs.push(format!( + "Variable `{}` has an empty prompt, which is not allowed", + var.name + )); + } + } + let type_str = var.default.type_str(); types.insert(var.name.to_string(), type_str); @@ -421,4 +439,81 @@ mod tests { assert_eq!(got_value, &Value::String(expected_value)) } + + #[test] + fn can_handle_derived_variable() { + let tpl: TemplateDefinition = toml::from_str( + r#" + name = "Test template" + description = "Testing derived variable behavior" + kickstart_version = 1 + + [[variables]] + name = "project_name" + default = "My project" + prompt = "What's the name of your project?" + + [[variables]] + name = "slug" + default = "{{project_name | slugify}}" + derived = true + "#, + ) + .unwrap(); + + assert_eq!(tpl.variables.len(), 2); + + let res = tpl.default_values(); + assert!(res.is_ok()); + let res = res.unwrap(); + + // Check that both variables exist + assert!(res.contains_key("project_name")); + assert!(res.contains_key("slug")); + + // Check that slug was rendered from project_name + let expected_slug = Value::String("my-project".to_string()); + assert_eq!(res.get("slug"), Some(&expected_slug)); + } + + #[test] + fn fails_if_prompt_and_derived_missing() { + let tpl: TemplateDefinition = toml::from_str( + r#" + name = "Test template" + kickstart_version = 1 + + [[variables]] + name = "broken_var" + default = "some_value" + "#, + ) + .unwrap(); + + let errs = tpl.validate(); + assert!(!errs.is_empty()); + assert!(errs + .iter() + .any(|e| e.contains("must have either a prompt or be marked as derived"))); + } + + #[test] + fn fails_if_prompt_is_empty() { + let tpl: TemplateDefinition = toml::from_str( + r#" + name = "Test template" + kickstart_version = 1 + + [[variables]] + name = "broken_var" + default = "some_value" + prompt = "" + "#, + ) + .unwrap(); + + let errs = tpl.validate(); + assert!(!errs.is_empty()); + assert!(errs.iter().any(|e| e.contains("empty prompt"))); + } } diff --git a/src/main.rs b/src/main.rs index 82ff73f..090991b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,30 +54,37 @@ fn ask_questions(template: &Template, no_input: bool) -> Result { - let res = if no_input { b } else { ask_bool(&var.prompt, b)? }; + let res = if no_input { b } else { ask_bool(prompt_text, b)? }; vals.insert(var.name.clone(), Value::Boolean(res)); continue; } Value::String(s) => { - let res = if no_input { s } else { ask_string(&var.prompt, &s, &var.validation)? }; + let res = if no_input { s } else { ask_string(prompt_text, &s, &var.validation)? }; vals.insert(var.name.clone(), Value::String(res)); continue; } Value::Integer(i) => { - let res = if no_input { i } else { ask_integer(&var.prompt, i)? }; + let res = if no_input { i } else { ask_integer(prompt_text, i)? }; vals.insert(var.name.clone(), Value::Integer(res)); continue; } From 922907ca17bdabc4d5e1d58451c44ebdacf5fe0f Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Tue, 16 Dec 2025 11:40:59 +0100 Subject: [PATCH 2/7] Update toml --- Cargo.lock | 74 +++++++++++++++++++++++++++++++----------------------- Cargo.toml | 2 +- 2 files changed, 44 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e98759c..6ebbe52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" @@ -378,9 +378,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" @@ -447,9 +447,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown", @@ -781,18 +781,28 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.215" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -813,11 +823,11 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.8" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -943,38 +953,43 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.19" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ - "serde", + "indexmap", + "serde_core", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_parser", + "toml_writer", + "winnow", ] [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" dependencies = [ - "serde", + "serde_core", ] [[package]] -name = "toml_edit" -version = "0.22.22" +name = "toml_parser" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", "winnow", ] +[[package]] +name = "toml_writer" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" + [[package]] name = "typenum" version = "1.17.0" @@ -1227,12 +1242,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.20" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" -dependencies = [ - "memchr", -] +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" [[package]] name = "zerocopy" diff --git a/Cargo.toml b/Cargo.toml index 96b24d2..5b58d53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ regex = "1" serde = {version = "1", features = ["derive"]} tera = "1" heck = "0.5" -toml = "0.8" +toml = "0.9" walkdir = "2" tempfile = "3" From a8b39a8bdc743e19825df3ef792ced3467ab5e2b Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Tue, 16 Dec 2025 11:54:42 +0100 Subject: [PATCH 3/7] Fix choices for bool/numbers Closes #81 --- Cargo.lock | 579 +++++++++++++++++++++++----------------------- Cargo.toml | 4 +- src/cli/prompt.rs | 6 +- src/definition.rs | 67 +++++- src/filters.rs | 2 +- src/generation.rs | 6 +- src/main.rs | 8 +- src/utils.rs | 10 +- src/value.rs | 19 +- 9 files changed, 377 insertions(+), 324 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ebbe52..049855c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,19 +4,13 @@ version = 4 [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -28,9 +22,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -43,55 +37,56 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", - "windows-sys 0.59.0", + "once_cell_polyfill", + "windows-sys 0.61.2", ] [[package]] name = "anyhow" -version = "1.0.94" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "block-buffer" @@ -104,9 +99,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ "memchr", "serde", @@ -114,41 +109,35 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - -[[package]] -name = "byteorder" -version = "1.5.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "cc" -version = "1.2.3" +version = "1.2.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" dependencies = [ + "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.39" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ - "android-tzdata", "iana-time-zone", "num-traits", - "windows-targets", + "windows-link", ] [[package]] @@ -175,9 +164,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.23" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", "clap_derive", @@ -185,9 +174,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.23" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", @@ -197,9 +186,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", @@ -209,26 +198,26 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "console" -version = "0.15.8" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" dependencies = [ "encode_unicode", - "lazy_static", "libc", - "windows-sys 0.52.0", + "once_cell", + "windows-sys 0.59.0", ] [[package]] @@ -239,18 +228,18 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -267,15 +256,15 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -283,9 +272,9 @@ dependencies = [ [[package]] name = "deunicode" -version = "1.6.0" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00" +checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" [[package]] name = "digest" @@ -299,24 +288,24 @@ dependencies = [ [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -325,6 +314,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + [[package]] name = "generic-array" version = "0.14.7" @@ -337,26 +332,38 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", "wasi", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + [[package]] name = "glob" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "globset" -version = "0.4.15" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" +checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" dependencies = [ "aho-corasick", "bstr", @@ -388,15 +395,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "home" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" -dependencies = [ - "windows-sys 0.52.0", -] - [[package]] name = "humansize" version = "2.1.3" @@ -408,14 +406,15 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", "windows-core", ] @@ -457,35 +456,34 @@ dependencies = [ [[package]] name = "insta" -version = "1.41.1" +version = "1.44.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9ffc4d4892617c50a928c52b2961cb5174b6fc6ebf252b2fac9d21955c48b8" +checksum = "b5c943d4415edd8153251b6f197de5eb1640e56d84e8d9159bea190421c73698" dependencies = [ "console", "globset", - "lazy_static", - "linked-hash-map", + "once_cell", "similar", "walkdir", ] [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" -version = "0.3.76" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", @@ -493,7 +491,7 @@ dependencies = [ [[package]] name = "kickstart" -version = "0.5.0" +version = "0.6.0" dependencies = [ "anyhow", "clap", @@ -518,39 +516,33 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.168" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libm" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" - -[[package]] -name = "linked-hash-map" -version = "0.5.6" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "log" -version = "0.4.22" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "num-traits" @@ -563,9 +555,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "parse-zoneinfo" @@ -578,26 +576,25 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.7.15" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" +checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" dependencies = [ "memchr", - "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.15" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" +checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f" dependencies = [ "pest", "pest_generator", @@ -605,9 +602,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.15" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" +checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625" dependencies = [ "pest", "pest_meta", @@ -618,29 +615,28 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.15" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" +checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82" dependencies = [ - "once_cell", "pest", "sha2", ] [[package]] name = "phf" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_shared", ] [[package]] name = "phf_codegen" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ "phf_generator", "phf_shared", @@ -648,9 +644,9 @@ dependencies = [ [[package]] name = "phf_generator" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", "rand", @@ -658,40 +654,46 @@ dependencies = [ [[package]] name = "phf_shared" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.8.5" @@ -719,14 +721,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.16", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -736,9 +738,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -747,28 +749,34 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rustix" -version = "0.38.42" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -811,14 +819,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] @@ -832,9 +841,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -849,15 +858,15 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "similar" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "siphasher" -version = "0.3.11" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slug" @@ -877,9 +886,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.90" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -888,22 +897,22 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.14.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ - "cfg-if", "fastrand", + "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tera" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab9d851b45e865f178319da0abdbfe6acbc4328759ff18dafc3a41c16b4cd2ee" +checksum = "e8004bca281f2d32df3bacd59bc67b312cb4c70cea46cbd79dbe8ac5ed206722" dependencies = [ "chrono", "chrono-tz", @@ -918,37 +927,16 @@ dependencies = [ "serde", "serde_json", "slug", - "unic-segment", + "unicode-segmentation", ] [[package]] name = "term" -version = "1.0.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4df4175de05129f31b80458c6df371a15e7fc3fd367272e6bf938e5c351c7ea0" +checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" dependencies = [ - "home", - "windows-sys 0.52.0", -] - -[[package]] -name = "thiserror" -version = "2.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "windows-sys 0.61.2", ] [[package]] @@ -992,9 +980,9 @@ checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" [[package]] name = "typenum" -version = "1.17.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "ucd-trie" @@ -1003,60 +991,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" -dependencies = [ - "unic-ucd-segment", -] - -[[package]] -name = "unic-ucd-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" +name = "unicode-ident" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] -name = "unicode-ident" -version = "1.0.14" +name = "unicode-segmentation" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "utf8parse" @@ -1082,40 +1026,37 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasm-bindgen" -version = "0.2.99" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "cfg-if", - "once_cell", - "wasm-bindgen-macro", + "wit-bindgen", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.99" +name = "wasm-bindgen" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1123,48 +1064,92 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "windows-core" -version = "0.52.0" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-targets", + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] -name = "windows-sys" -version = "0.52.0" +name = "windows-implement" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ - "windows-targets", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", ] [[package]] @@ -1176,6 +1161,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -1246,21 +1240,26 @@ version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 5b58d53..99679b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] authors = ["Vincent Prouillet "] description = "A simple way to get started with a project by scaffolding from a template powered by the Tera engine" -edition = "2021" +edition = "2024" keywords = ["tera", "scaffolding", "templating", "generator", "boilerplate"] license = "MIT" name = "kickstart" -version = "0.5.0" +version = "0.6.0" [dependencies] glob = "0.3" diff --git a/src/cli/prompt.rs b/src/cli/prompt.rs index edf6370..8c5db0f 100644 --- a/src/cli/prompt.rs +++ b/src/cli/prompt.rs @@ -1,8 +1,8 @@ use std::io::{self, BufRead, Write}; -use crate::cli::terminal; -use crate::errors::{new_error, ErrorKind, Result}; use crate::Value; +use crate::cli::terminal; +use crate::errors::{ErrorKind, Result, new_error}; use regex::Regex; /// Wait for user input and return what they typed @@ -85,7 +85,7 @@ pub fn ask_choices(prompt: &str, default: &Value, choices: &[Value]) -> Result { - if !re.is_match(var.default.as_str().unwrap()) { + if !re.is_match(&var.default.as_string()) { errs.push(format!( "Variable `{}` has a default that doesn't pass its validation regex", var.name @@ -476,6 +485,28 @@ mod tests { assert_eq!(res.get("slug"), Some(&expected_slug)); } + #[test] + fn can_handle_number_choices() { + let tpl: TemplateDefinition = toml::from_str( + r#" + name = "Test template" + description = "A description" + kickstart_version = 1 + + [[variables]] + name = "count" + prompt = "How many?" + default = 10 + choices = [1, 4, 10] + "#, + ) + .unwrap(); + + assert_eq!(tpl.variables.len(), 1); + let res = tpl.default_values(); + assert!(res.is_ok()); + } + #[test] fn fails_if_prompt_and_derived_missing() { let tpl: TemplateDefinition = toml::from_str( @@ -492,9 +523,9 @@ mod tests { let errs = tpl.validate(); assert!(!errs.is_empty()); - assert!(errs - .iter() - .any(|e| e.contains("must have either a prompt or be marked as derived"))); + assert!( + errs.iter().any(|e| e.contains("must have either a prompt or be marked as derived")) + ); } #[test] @@ -516,4 +547,26 @@ mod tests { assert!(!errs.is_empty()); assert!(errs.iter().any(|e| e.contains("empty prompt"))); } + + #[test] + fn fails_with_choices_for_bool_type() { + let tpl: TemplateDefinition = toml::from_str( + r#" + name = "Test template" + description = "A description" + kickstart_version = 1 + + [[variables]] + name = "truthy" + prompt = "Is it true?" + default = false + choices = [true, false] + "#, + ) + .unwrap(); + + let errs = tpl.validate(); + assert!(!errs.is_empty()); + assert!(errs.iter().any(|e| e.contains("boolean"))); + } } diff --git a/src/filters.rs b/src/filters.rs index 6935079..21f980c 100644 --- a/src/filters.rs +++ b/src/filters.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use heck::*; -use tera::{to_value, try_get_value, Result, Tera, Value}; +use tera::{Result, Tera, Value, to_value, try_get_value}; pub fn register_all_filters(tera: &mut Tera) { tera.register_filter("upper_camel_case", upper_camel_case); diff --git a/src/generation.rs b/src/generation.rs index bc47493..2f889e7 100644 --- a/src/generation.rs +++ b/src/generation.rs @@ -9,14 +9,14 @@ use std::process::Command; use std::str; use glob::Pattern; -use tempfile::{tempdir, TempDir}; +use tempfile::{TempDir, tempdir}; use tera::Context; use walkdir::WalkDir; use crate::definition::{Hook, TemplateDefinition}; -use crate::errors::{map_io_err, new_error, ErrorKind, Result}; +use crate::errors::{ErrorKind, Result, map_io_err, new_error}; use crate::utils::{ - create_directory, get_source, is_binary, read_file, render_one_off_template, write_file, Source, + Source, create_directory, get_source, is_binary, read_file, render_one_off_template, write_file, }; use crate::{Value, Variable}; diff --git a/src/main.rs b/src/main.rs index 090991b..4d80be1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::path::PathBuf; use std::process::Command as StdCommand; -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use clap::{Parser, Subcommand}; use kickstart::cli::prompt::{ask_bool, ask_choices, ask_integer, ask_string}; @@ -101,11 +101,7 @@ fn execute_hook(hook: &HookFile, output_dir: &PathBuf) -> Result<()> { command.current_dir(output_dir); } let code = command.status()?; - if code.success() { - Ok(()) - } else { - bail!("Hook `{}` exited with a non 0 code\n", hook.name()) - } + if code.success() { Ok(()) } else { bail!("Hook `{}` exited with a non 0 code\n", hook.name()) } } fn try_main() -> Result<()> { diff --git a/src/utils.rs b/src/utils.rs index a94f029..20f92f3 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,11 +1,11 @@ -use std::fs::{create_dir_all, File}; +use std::fs::{File, create_dir_all}; use std::io::prelude::*; use std::path::{Path, PathBuf}; use memchr::memchr; use tera::{Context, Tera}; -use crate::errors::{map_io_err, new_error, ErrorKind, Result}; +use crate::errors::{ErrorKind, Result, map_io_err, new_error}; use crate::filters::register_all_filters; #[derive(Debug, Clone, Eq, PartialEq)] @@ -42,11 +42,7 @@ pub fn create_directory(path: &Path) -> Result<()> { pub fn get_source(input: &str) -> Source { let path = Path::new(input); - if path.is_dir() { - Source::Local(path.to_path_buf()) - } else { - Source::Git(input.to_string()) - } + if path.is_dir() { Source::Local(path.to_path_buf()) } else { Source::Git(input.to_string()) } } pub fn render_one_off_template( diff --git a/src/value.rs b/src/value.rs index 32bde7e..a09a80c 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,4 +1,4 @@ -use serde::{de::Error, Deserialize, Deserializer, Serialize}; +use serde::{Deserialize, Deserializer, Serialize, de::Error}; use std::fmt::Formatter; use toml::Value as TomlValue; @@ -34,10 +34,15 @@ impl Value { matches!(self, Value::String(..)) } - pub(crate) fn as_str(&self) -> Option<&str> { + pub(crate) fn is_bool(&self) -> bool { + matches!(self, Value::Boolean(..)) + } + + pub(crate) fn as_string(&self) -> String { match *self { - Value::String(ref s) => Some(&**s), - _ => None, + Value::String(ref s) => s.to_string(), + Value::Integer(i) => i.to_string(), + Value::Boolean(b) => b.to_string(), } } } @@ -52,7 +57,11 @@ impl<'de> Deserialize<'de> for Value { TomlValue::String(s) => Ok(Value::String(s)), TomlValue::Integer(i) => Ok(Value::Integer(i)), TomlValue::Boolean(b) => Ok(Value::Boolean(b)), - _ => Err(D::Error::custom(format!("Value {} (of type `{}`) is not allowed as a value: only strings, integers and boolean are.", v, v.type_str()))), + _ => Err(D::Error::custom(format!( + "Value {} (of type `{}`) is not allowed as a value: only strings, integers and boolean are.", + v, + v.type_str() + ))), } } } From 731b7305986e03fcef0f9f08a8d7c6a50c6ca032 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Tue, 16 Dec 2025 11:57:02 +0100 Subject: [PATCH 4/7] Update gh actions --- .github/workflows/ci.yaml | 11 ++++------- .github/workflows/release.yml | 10 +++++----- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7e7cf89..52931ad 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -11,22 +11,19 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - build: [msrv, stable, macos, win-msvc] + build: [stable, macos, windows] include: - - build: msrv - os: ubuntu-latest - rust: 1.74.0 - build: stable os: ubuntu-latest rust: stable - build: macos os: macOS-latest rust: stable - - build: win-msvc - os: windows-2019 + - build: windows + os: windows-latest rust: stable steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - name: Install Rust uses: dtolnay/rust-toolchain@master with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 780d2f1..fc6b2ba 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,17 +30,17 @@ jobs: include: - os: windows-2022 target: x86_64-pc-windows-msvc - - os: ubuntu-20.04 + - os: ubuntu-22.04 target: x86_64-unknown-linux-gnu - - os: ubuntu-20.04 + - os: ubuntu-22.04 target: aarch64-unknown-linux-gnu - - os: macos-13 - target: x86_64-apple-darwin - os: macos-14 + target: x86_64-apple-darwin + - os: macos-15 target: aarch64-apple-darwin steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install Rust uses: dtolnay/rust-toolchain@stable From ac9001e27ba3244b5947b7e41db0ef8ba718c096 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Tue, 16 Dec 2025 13:34:52 +0100 Subject: [PATCH 5/7] Add JSON input file Closes #50 --- .github/workflows/ci.yaml | 1 + Cargo.lock | 1 + Cargo.toml | 3 +- README.md | 3 +- examples/super-basic/template.toml | 2 + examples/super-basic/test-input.json | 5 ++ src/cli/file_input.rs | 88 ++++++++++++++++++++++++++++ src/cli/mod.rs | 1 + src/cli/prompt.rs | 2 +- src/cli/terminal.rs | 4 +- src/generation.rs | 2 +- src/main.rs | 52 ++++++++++++---- 12 files changed, 146 insertions(+), 18 deletions(-) create mode 100644 examples/super-basic/test-input.json create mode 100644 src/cli/file_input.rs diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 52931ad..351fb1e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -41,5 +41,6 @@ jobs: cargo run --features=cli -- examples/default-from-variable/ --no-input -o default cargo run --features=cli -- examples/slugify/ --no-input -o slugify cargo run --features=cli -- examples/super-basic/ --no-input -o super-basic + cargo run --features=cli -- examples/super-basic/ -i examples/super-basic/test-input.json -o super-basic-json cargo run --features=cli -- examples/with-directory/ --no-input -o with-directory cargo run --features=cli -- examples/derived/ --no-input -o derived \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 049855c..d140cd4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -501,6 +501,7 @@ dependencies = [ "memchr", "regex", "serde", + "serde_json", "tempfile", "tera", "term", diff --git a/Cargo.toml b/Cargo.toml index 99679b5..0e785e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,12 +21,13 @@ tempfile = "3" clap = { version = "4", features = ["derive"], optional = true } term = { version = "1", optional = true } anyhow = { version = "1", optional = true } +serde_json = { version = "1", optional = true } [dev-dependencies] insta = { version = "1.38.0", features = ["glob"] } [features] -cli = ["dep:clap", "dep:term", "dep:anyhow"] +cli = ["dep:clap", "dep:term", "dep:anyhow", "dep:serde_json"] required-features = ["cli"] diff --git a/README.md b/README.md index 14d754c..c221848 100644 --- a/README.md +++ b/README.md @@ -236,10 +236,11 @@ You can use these like any other filter, e.g. `{{variable_name | camel_case}}`. ## Changelog -### 0.5.1 (2025-12-08) +### 0.6.0 (2025-12-08) - New `derived = true` flag allows variables to be computed from default without prompting the user - `prompt` remains required for non-derived variables, but is optional when `derived` is set to true +- Allow using a JSON file as input for the values for the CLI ### 0.5.0 (2024-12-13) diff --git a/examples/super-basic/template.toml b/examples/super-basic/template.toml index 300f73d..8f6b565 100644 --- a/examples/super-basic/template.toml +++ b/examples/super-basic/template.toml @@ -2,6 +2,8 @@ name = "Super basic" description = "A very simple template" kickstart_version = 1 +ignore = ["test-input.json"] + [[variables]] name = "directory_name" default = "Hello" diff --git a/examples/super-basic/test-input.json b/examples/super-basic/test-input.json new file mode 100644 index 0000000..0c443c7 --- /dev/null +++ b/examples/super-basic/test-input.json @@ -0,0 +1,5 @@ +{ + "directory_name": "TestDir", + "file_name": "TestFile", + "greeting_recipient": "World" +} diff --git a/src/cli/file_input.rs b/src/cli/file_input.rs new file mode 100644 index 0000000..735c3fc --- /dev/null +++ b/src/cli/file_input.rs @@ -0,0 +1,88 @@ +use std::collections::HashMap; +use std::path::Path; + +use crate::cli::terminal; +use crate::{Template, Value}; +use anyhow::{Context, Result, bail}; +use regex::Regex; + +/// Load and validate variable values from a JSON file +pub fn load_json_input(path: &Path, template: &Template) -> Result> { + let content = std::fs::read_to_string(path) + .with_context(|| format!("Failed to read input file: {}", path.display()))?; + + let json: serde_json::Value = + serde_json::from_str(&content).with_context(|| "Failed to parse JSON input file")?; + + let obj = json.as_object().ok_or_else(|| anyhow::anyhow!("JSON input must be an object"))?; + + let mut values = HashMap::new(); + for (key, json_val) in obj { + // Check if this variable exists in the template + let var = match template.get_variable_by_name(key) { + Ok(v) => v, + Err(_) => { + terminal::error(&format!( + "Warning: Unknown variable '{}' in JSON input (ignored)\n", + key + )); + continue; + } + }; + + // Convert JSON value to kickstart Value + let value = match json_val { + serde_json::Value::String(s) => Value::String(s.clone()), + serde_json::Value::Number(n) => { + if let Some(i) = n.as_i64() { + Value::Integer(i) + } else { + bail!("Invalid value for '{}': number {} is not a valid integer", key, n); + } + } + serde_json::Value::Bool(b) => Value::Boolean(*b), + _ => bail!( + "Invalid value for '{}': only strings, integers and booleans are supported", + key + ), + }; + + // Type check + if var.default.type_str() != value.type_str() { + bail!( + "Variable '{}' expects type '{}', but got '{}'", + key, + var.default.type_str(), + value.type_str() + ); + } + + // Choices validation + if let Some(ref choices) = var.choices { + if !choices.contains(&value) { + let choices_str: Vec<_> = choices.iter().map(|c| format!("{}", c)).collect(); + bail!( + "Variable '{}' value '{}' is not in allowed choice: [{}]", + key, + value, + choices_str.join(", ") + ); + } + } + + // Regex validation + if let Some(ref pattern) = var.validation { + if let Value::String(s) = &value { + let re = Regex::new(pattern) + .with_context(|| format!("Invalid validation regex for '{}'", key))?; + if !re.is_match(s) { + bail!("Variable '{}' value '{}' needs to pass the regex: {}", key, s, pattern); + } + } + } + + values.insert(key.clone(), value); + } + + Ok(values) +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index d08d4bb..aca1bb5 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,2 +1,3 @@ +pub mod file_input; pub mod prompt; pub mod terminal; diff --git a/src/cli/prompt.rs b/src/cli/prompt.rs index 8c5db0f..a47d311 100644 --- a/src/cli/prompt.rs +++ b/src/cli/prompt.rs @@ -41,7 +41,7 @@ pub fn ask_string(prompt: &str, default: &str, validation: &Option) -> R let res = match &*input { "" => default.to_string(), _ => { - if let Some(ref pattern) = validation { + if let Some(pattern) = validation { let re = Regex::new(pattern).unwrap(); if re.is_match(&input) { input diff --git a/src/cli/terminal.rs b/src/cli/terminal.rs index 3bb77c4..ac587bd 100644 --- a/src/cli/terminal.rs +++ b/src/cli/terminal.rs @@ -54,7 +54,7 @@ pub fn basic_question(prompt: &str, default: &T, validation: &O if let Some(mut t) = term::stdout() { // check for colour/boldness at the beginning so we can unwrap later if !t.supports_color() || !t.supports_attr(term::Attr::Bold) { - if let Some(ref pattern) = validation { + if let Some(pattern) = validation { write!(t, "{} [default: {}, validation: {}]: ", prompt, default, pattern).unwrap(); } else { write!(t, "{} [default: {}]: ", prompt, default).unwrap(); @@ -66,7 +66,7 @@ pub fn basic_question(prompt: &str, default: &T, validation: &O write!(t, "{} ", prompt).unwrap(); t.reset().unwrap(); t.fg(term::color::YELLOW).unwrap(); - if let Some(ref pattern) = validation { + if let Some(pattern) = validation { write!(t, "[default: {}, validation: {}]: ", default, pattern).unwrap(); } else { write!(t, "[default: {}]: ", default).unwrap(); diff --git a/src/generation.rs b/src/generation.rs index 2f889e7..dcd4ccb 100644 --- a/src/generation.rs +++ b/src/generation.rs @@ -106,7 +106,7 @@ impl Template { Ok(Template { path: buf, definition, variables: HashMap::new(), tmp_dir: tempdir()? }) } - fn get_variable_by_name(&self, name: &str) -> Result<&Variable> { + pub(crate) fn get_variable_by_name(&self, name: &str) -> Result<&Variable> { if let Some(var) = self.definition.variables.iter().find(|v| v.name == name) { Ok(var) } else { diff --git a/src/main.rs b/src/main.rs index 4d80be1..21bbe5a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use std::process::Command as StdCommand; use anyhow::{Result, bail}; use clap::{Parser, Subcommand}; +use kickstart::cli::file_input::load_json_input; use kickstart::cli::prompt::{ask_bool, ask_choices, ask_integer, ask_string}; use kickstart::cli::terminal; use kickstart::{HookFile, Template, TemplateDefinition, Value}; @@ -30,6 +31,10 @@ pub struct Cli { #[clap(long, default_value_t = false)] pub no_input: bool, + /// Path to a JSON file containing variable values (implies --no-input) + #[clap(short = 'i', long = "input-file", value_name = "PATH")] + pub input_file: Option, + /// Whether to run all the hooks #[clap(long, default_value_t = true)] pub run_hooks: bool, @@ -48,9 +53,15 @@ pub enum Command { } /// Ask all the questions of that template and return the answers. -/// If `no_input` is `true`, it will automatically pick the defaults without -/// prompting the user -fn ask_questions(template: &Template, no_input: bool) -> Result> { +/// If a value exists in `provided_values`, it will be validated and used. +/// Otherwise: +/// - if `no_input` is `true`, the default is used without prompting. +/// - else the user is prompted interactively. +fn ask_questions( + template: &Template, + no_input: bool, + provided_values: &HashMap, +) -> Result> { let mut vals = HashMap::new(); for var in &template.definition.variables { @@ -63,30 +74,42 @@ fn ask_questions(template: &Template, no_input: bool) -> Result use the default + if no_input { + vals.insert(var.name.clone(), default); + continue; + } + + // Interactive prompting let prompt_text = var.prompt.as_deref().unwrap_or(""); if let Some(ref choices) = var.choices { - let res = if no_input { default } else { ask_choices(prompt_text, &default, choices)? }; + let res = ask_choices(prompt_text, &default, choices)?; vals.insert(var.name.clone(), res); continue; } match default { Value::Boolean(b) => { - let res = if no_input { b } else { ask_bool(prompt_text, b)? }; + let res = ask_bool(prompt_text, b)?; vals.insert(var.name.clone(), Value::Boolean(res)); - continue; } Value::String(s) => { - let res = if no_input { s } else { ask_string(prompt_text, &s, &var.validation)? }; + let res = ask_string(prompt_text, &s, &var.validation)?; vals.insert(var.name.clone(), Value::String(res)); - continue; } Value::Integer(i) => { - let res = if no_input { i } else { ask_integer(prompt_text, i)? }; + let res = ask_integer(prompt_text, i)?; vals.insert(var.name.clone(), Value::Integer(res)); - continue; } } } @@ -126,8 +149,13 @@ fn try_main() -> Result<()> { let mut template = Template::from_input(&cli.template.unwrap(), cli.directory.as_deref())?; - // 1. ask questions - let vals = ask_questions(&template, cli.no_input)?; + // 1. collect variables (from JSON input or interactive prompts) + let (no_input, provided_values) = if let Some(ref input_path) = cli.input_file { + (true, load_json_input(input_path, &template)?) + } else { + (cli.no_input, HashMap::new()) + }; + let vals = ask_questions(&template, no_input, &provided_values)?; template.set_variables(vals)?; // 2. run pre-gen hooks From f595224fd9c02f9765f41e7ec5238915e11693e1 Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Tue, 16 Dec 2025 13:43:45 +0100 Subject: [PATCH 6/7] clippy --- src/cli/file_input.rs | 34 ++++++++++++++++----------------- src/definition.rs | 17 ++++++++--------- src/generation.rs | 44 +++++++++++++++++++++---------------------- 3 files changed, 47 insertions(+), 48 deletions(-) diff --git a/src/cli/file_input.rs b/src/cli/file_input.rs index 735c3fc..30535a7 100644 --- a/src/cli/file_input.rs +++ b/src/cli/file_input.rs @@ -58,26 +58,26 @@ pub fn load_json_input(path: &Path, template: &Template) -> Result = choices.iter().map(|c| format!("{}", c)).collect(); - bail!( - "Variable '{}' value '{}' is not in allowed choice: [{}]", - key, - value, - choices_str.join(", ") - ); - } + if let Some(ref choices) = var.choices + && !choices.contains(&value) + { + let choices_str: Vec<_> = choices.iter().map(|c| format!("{}", c)).collect(); + bail!( + "Variable '{}' value '{}' is not in allowed choice: [{}]", + key, + value, + choices_str.join(", ") + ); } // Regex validation - if let Some(ref pattern) = var.validation { - if let Value::String(s) = &value { - let re = Regex::new(pattern) - .with_context(|| format!("Invalid validation regex for '{}'", key))?; - if !re.is_match(s) { - bail!("Variable '{}' value '{}' needs to pass the regex: {}", key, s, pattern); - } + if let Some(ref pattern) = var.validation + && let Value::String(s) = &value + { + let re = Regex::new(pattern) + .with_context(|| format!("Invalid validation regex for '{}'", key))?; + if !re.is_match(s) { + bail!("Variable '{}' value '{}' needs to pass the regex: {}", key, s, pattern); } } diff --git a/src/definition.rs b/src/definition.rs index a3b79be..8f07d6d 100644 --- a/src/definition.rs +++ b/src/definition.rs @@ -141,13 +141,13 @@ impl TemplateDefinition { )); } - if let Some(ref prompt) = var.prompt { - if prompt.trim().is_empty() { - errs.push(format!( - "Variable `{}` has an empty prompt, which is not allowed", - var.name - )); - } + if let Some(ref prompt) = var.prompt + && prompt.trim().is_empty() + { + errs.push(format!( + "Variable `{}` has an empty prompt, which is not allowed", + var.name + )); } let type_str = var.default.type_str(); @@ -276,14 +276,13 @@ impl TemplateDefinition { #[cfg(test)] mod tests { - use toml; use super::*; #[test] fn can_validate_definition() { insta::glob!("snapshots/validation/*.toml", |path| { - match TemplateDefinition::validate_file(&path) { + match TemplateDefinition::validate_file(path) { Ok(errs) => insta::assert_debug_snapshot!(&errs), Err(e) => insta::assert_snapshot!(&e), } diff --git a/src/generation.rs b/src/generation.rs index dcd4ccb..c2e2df5 100644 --- a/src/generation.rs +++ b/src/generation.rs @@ -74,7 +74,7 @@ impl Template { pub fn from_git(remote: &str, directory: Option<&str>) -> Result