@@ -17,6 +17,7 @@ use moon_console::{
1717} ;
1818use rustc_hash:: FxHashMap ;
1919use starbase:: AppResult ;
20+ use starbase_utils:: json:: { self , JsonValue , serde_json} ;
2021use std:: path:: PathBuf ;
2122use std:: sync:: Arc ;
2223use tera:: Context as TemplateContext ;
@@ -53,6 +54,28 @@ pub struct GenerateArgs {
5354 vars : Vec < String > ,
5455}
5556
57+ fn is_numeric ( value : & str ) -> bool {
58+ value
59+ . as_bytes ( )
60+ . iter ( )
61+ . all ( |b| * b == b'-' || * b == b'.' || * b >= b'0' && * b <= b'9' )
62+ }
63+
64+ fn parse_arg_into_json ( value : & str ) -> miette:: Result < JsonValue > {
65+ if value == "true"
66+ || value == "false"
67+ || value == "null"
68+ || value. starts_with ( '[' )
69+ || value. starts_with ( '{' )
70+ || value. starts_with ( '"' )
71+ || is_numeric ( value)
72+ {
73+ Ok ( json:: parse ( value) ?)
74+ } else {
75+ Ok ( JsonValue :: String ( value. into ( ) ) )
76+ }
77+ }
78+
5679#[ instrument( skip( config) ) ]
5780pub fn parse_args_into_variables (
5881 args : & [ String ] ,
@@ -76,6 +99,14 @@ pub fn parse_args_into_variables(
7699 }
77100
78101 match cfg {
102+ TemplateVariable :: Array ( _) => {
103+ command = command. arg (
104+ Arg :: new ( name)
105+ . long ( name)
106+ . action ( ArgAction :: Append )
107+ . value_parser ( StringValueParser :: new ( ) ) ,
108+ ) ;
109+ }
79110 TemplateVariable :: Boolean ( _) => {
80111 command = command. arg (
81112 Arg :: new ( name)
@@ -114,6 +145,9 @@ pub fn parse_args_into_variables(
114145 . allow_negative_numbers ( true ) ,
115146 ) ;
116147 }
148+ TemplateVariable :: Object ( _) => {
149+ debug ! ( "Skipping object based arguments" ) ;
150+ }
117151 TemplateVariable :: String ( _) => {
118152 command = command. arg (
119153 Arg :: new ( name)
@@ -146,6 +180,25 @@ pub fn parse_args_into_variables(
146180 }
147181
148182 match cfg {
183+ TemplateVariable :: Array ( _) => {
184+ let mut list: Vec < JsonValue > = vec ! [ ] ;
185+
186+ if let Some ( value) = matches. get_many :: < String > ( arg_name) {
187+ let value = value. collect :: < Vec < _ > > ( ) ;
188+
189+ debug ! (
190+ name,
191+ value = ?value,
192+ "Setting array variable"
193+ ) ;
194+
195+ for item in value {
196+ list. push ( parse_arg_into_json ( item) ?) ;
197+ }
198+ }
199+
200+ vars. insert ( name, & list) ;
201+ }
149202 TemplateVariable :: Boolean ( _) => {
150203 // Booleans always have a value when matched, so only extract
151204 // the value when it was actually passed on the command line
@@ -183,6 +236,9 @@ pub fn parse_args_into_variables(
183236 vars. insert ( name, value) ;
184237 }
185238 }
239+ TemplateVariable :: Object ( _) => {
240+ vars. insert ( name, & FxHashMap :: < String , JsonValue > :: default ( ) ) ;
241+ }
186242 TemplateVariable :: String ( _) => {
187243 if let Some ( value) = matches. get_one :: < String > ( arg_name) {
188244 debug ! ( name, value, "Setting string variable" ) ;
@@ -246,6 +302,54 @@ pub async fn gather_variables(
246302 let required = config. is_required ( ) ;
247303
248304 match config {
305+ TemplateVariable :: Array ( cfg) => {
306+ let value = if skip_prompts || cfg. prompt . is_none ( ) {
307+ cfg. default . clone ( )
308+ } else {
309+ let mut value = String :: new ( ) ;
310+
311+ console
312+ . render_interactive ( element ! {
313+ Input (
314+ label: cfg. prompt. as_ref( ) . unwrap( ) ,
315+ description: Some ( "As a JSON string" . into( ) ) ,
316+ on_value: & mut value,
317+ validate: move |input: String | {
318+ let input = if input. is_empty( ) {
319+ if required {
320+ return Some ( "A value is required" . into( ) ) ;
321+ } else {
322+ "[]"
323+ }
324+ } else {
325+ & input
326+ } ;
327+
328+ match serde_json:: from_str:: <JsonValue >( input) {
329+ Ok ( data) => if data. is_array( ) {
330+ None
331+ } else {
332+ Some ( "Must be an array" . into( ) )
333+ } ,
334+ Err ( error) => Some ( format!( "Invalid JSON: {error}" ) ) ,
335+ }
336+ }
337+ )
338+ } )
339+ . await ?;
340+
341+ let data: JsonValue = json:: parse ( value) ?;
342+
343+ match data {
344+ JsonValue :: Array ( inner) => inner,
345+ _ => vec ! [ ] ,
346+ }
347+ } ;
348+
349+ debug ! ( name, value = ?value, "Setting array variable" ) ;
350+
351+ context. insert ( name, & value) ;
352+ }
249353 TemplateVariable :: Boolean ( cfg) => {
250354 let value = if skip_prompts || cfg. prompt . is_none ( ) {
251355 cfg. default
@@ -302,6 +406,54 @@ pub async fn gather_variables(
302406
303407 context. insert ( name, & value) ;
304408 }
409+ TemplateVariable :: Object ( cfg) => {
410+ let value = if skip_prompts || cfg. prompt . is_none ( ) {
411+ cfg. default . clone ( )
412+ } else {
413+ let mut value = String :: new ( ) ;
414+
415+ console
416+ . render_interactive ( element ! {
417+ Input (
418+ label: cfg. prompt. as_ref( ) . unwrap( ) ,
419+ description: Some ( "As a JSON string" . into( ) ) ,
420+ on_value: & mut value,
421+ validate: move |input: String | {
422+ let input = if input. is_empty( ) {
423+ if required {
424+ return Some ( "A value is required" . into( ) ) ;
425+ } else {
426+ "{}"
427+ }
428+ } else {
429+ & input
430+ } ;
431+
432+ match serde_json:: from_str:: <JsonValue >( input) {
433+ Ok ( data) => if data. is_object( ) {
434+ None
435+ } else {
436+ Some ( "Must be an object" . into( ) )
437+ } ,
438+ Err ( error) => Some ( format!( "Invalid JSON: {error}" ) ) ,
439+ }
440+ }
441+ )
442+ } )
443+ . await ?;
444+
445+ let data: JsonValue = json:: parse ( value) ?;
446+
447+ match data {
448+ JsonValue :: Object ( inner) => FxHashMap :: from_iter ( inner) ,
449+ _ => FxHashMap :: default ( ) ,
450+ }
451+ } ;
452+
453+ debug ! ( name, value = ?value, "Setting object variable" ) ;
454+
455+ context. insert ( name, & value) ;
456+ }
305457 TemplateVariable :: String ( cfg) => {
306458 let value = if skip_prompts || cfg. prompt . is_none ( ) {
307459 cfg. default . clone ( )
0 commit comments