diff --git a/module/move/wca/examples/wca_trivial.rs b/module/move/wca/examples/wca_trivial.rs index 272923ecf5..c228e6e20a 100644 --- a/module/move/wca/examples/wca_trivial.rs +++ b/module/move/wca/examples/wca_trivial.rs @@ -2,7 +2,7 @@ //! A trivial example. //! -use wca::{ CommandsAggregator, Type, VerifiedCommand }; +use wca::{ CommandsAggregator, Order, Type, VerifiedCommand }; fn f1( o : VerifiedCommand ) { @@ -19,16 +19,17 @@ fn exit() fn main() { let ca = CommandsAggregator::former() + .command( "exit" ) + .hint( "just exit" ) + .routine( || exit() ) + .end() .command( "echo" ) .hint( "prints all subjects and properties" ) .subject().hint( "Subject" ).kind( Type::String ).optional( true ).end() .property( "property" ).hint( "simple property" ).kind( Type::String ).optional( true ).end() .routine( f1 ) .end() - .command( "exit" ) - .hint( "just exit" ) - .routine( || exit() ) - .end() + .order( Order::Lexicography ) .perform() ; diff --git a/module/move/wca/src/ca/aggregator.rs b/module/move/wca/src/ca/aggregator.rs index 1174de448f..8336b1df29 100644 --- a/module/move/wca/src/ca/aggregator.rs +++ b/module/move/wca/src/ca/aggregator.rs @@ -26,6 +26,17 @@ pub( crate ) mod private for_lib::*, }; use wtools::Itertools; + + /// Order of commands and properties. + #[ derive( Debug, Default, Clone, Copy, Eq, PartialOrd, PartialEq ) ] + pub enum Order + { + /// Natures order. + #[ default ] + Nature, + /// Lexicography order. + Lexicography, + } /// Validation errors that can occur in application. #[ derive( Error, Debug ) ] @@ -100,7 +111,7 @@ pub( crate ) mod private /// ``` #[ derive( Debug ) ] #[ derive( former::Former ) ] - #[ storage_fields( help_generator : HelpGeneratorFn, help_variants : HashSet< HelpVariants > ) ] + #[ storage_fields( help_generator : HelpGeneratorFn, help_variants : HashSet< HelpVariants >, order : Order ) ] #[ mutator( custom = true ) ] // #[ debug ] pub struct CommandsAggregator @@ -127,19 +138,20 @@ pub( crate ) mod private { let ca = storage; let dictionary = ca.dictionary.get_or_insert_with( Dictionary::default ); + dictionary.order = ca.order.unwrap_or_default(); let help_generator = std::mem::take( &mut ca.help_generator ).unwrap_or_default(); let help_variants = std::mem::take( &mut ca.help_variants ).unwrap_or_else( || HashSet::from([ HelpVariants::All ]) ); if help_variants.contains( &HelpVariants::All ) { - HelpVariants::All.generate( &help_generator, dictionary ); + HelpVariants::All.generate( &help_generator, dictionary, ca.order.unwrap_or_default() ); } else { for help in help_variants.iter().sorted() { - help.generate( &help_generator, dictionary ); + help.generate( &help_generator, dictionary, ca.order.unwrap_or_default() ); } } } @@ -158,6 +170,7 @@ pub( crate ) mod private where IntoName : Into< String >, { + let name = name.into(); let on_end = | command : CommandFormerStorage, super_former : Option< Self > | -> Self { let mut super_former = super_former.unwrap(); @@ -279,4 +292,5 @@ crate::mod_interface! exposed use CommandsAggregatorFormer; exposed use Error; exposed use ValidationError; + exposed use Order; } diff --git a/module/move/wca/src/ca/formatter.rs b/module/move/wca/src/ca/formatter.rs index d21979acdf..368ec8e88f 100644 --- a/module/move/wca/src/ca/formatter.rs +++ b/module/move/wca/src/ca/formatter.rs @@ -3,6 +3,7 @@ pub( crate ) mod private use crate::*; use wtools::Itertools; + use ca::aggregator::private::Order; /// - #[ derive( Debug, Clone, PartialEq ) ] @@ -12,18 +13,18 @@ pub( crate ) mod private Another, } - pub fn md_generator( grammar : &Dictionary ) -> String + pub fn md_generator( grammar : &Dictionary, order: Order ) -> String { - let text = grammar.commands - .iter() - .sorted_by_key( |( name, _ )| *name ) + let text = grammar.commands() + .into_iter() .map( |( name, cmd )| { let subjects = cmd.subjects.iter().fold( String::new(), | _, _ | format!( " `[argument]`" ) ); let properties = if cmd.properties.is_empty() { " " } else { " `[properties]` " }; format! ( - "[.{name}{subjects}{properties}](#{}{}{})", + "[.{}{subjects}{properties}](#{}{}{})", + name, name.replace( '.', "" ), if cmd.subjects.is_empty() { "" } else { "-argument" }, if cmd.properties.is_empty() { "" } else { "-properties" }, @@ -36,16 +37,15 @@ pub( crate ) mod private let list_of_commands = format!( "## Commands\n\n{}", text ); - let about_each_command = grammar.commands - .iter() - .sorted_by_key( |( name, _ )| *name ) + let about_each_command = grammar.commands() + .into_iter() .map( |( name, cmd )| { let subjects = cmd.subjects.iter().fold( String::new(), | _, _ | format!( " `[Subject]`" ) ); let properties = if cmd.properties.is_empty() { " " } else { " `[properties]` " }; let hint = if cmd.hint.is_empty() { &cmd.long_hint } else { &cmd.hint }; - let heading = format!( "## .{name}{subjects}{properties}\n__{}__\n", hint ); + let heading = format!( "## .{}{subjects}{properties}\n__{}__\n", name, hint ); let hint = if cmd.long_hint.is_empty() { &cmd.hint } else { &cmd.long_hint }; let full_subjects = cmd @@ -59,13 +59,12 @@ pub( crate ) mod private ) .join( "\n" ); let full_properties = cmd - .properties - .iter() - .sorted_by_key( |( name, _ )| *name ) + .properties( order ) + .into_iter() .map ( |( name, value )| - format!( "\n- {}{name} - {} `[{:?}]`", if value.optional { "`< optional >` " } else { "" }, value.hint, value.kind ) + format!( "\n- {}{} - {} `[{:?}]`", if value.optional { "`< optional >` " } else { "" }, value.hint, name, value.kind ) ) .join( "\n" ); // aaa : for Bohdan : toooooo log lines. 130 is max diff --git a/module/move/wca/src/ca/grammar/command.rs b/module/move/wca/src/ca/grammar/command.rs index 54274df86d..3d3e5cd2b4 100644 --- a/module/move/wca/src/ca/grammar/command.rs +++ b/module/move/wca/src/ca/grammar/command.rs @@ -1,11 +1,10 @@ pub( crate ) mod private { use crate::*; - - use { Handler, Routine, Type }; - - use std::collections::HashMap; + + use std::collections::{ BTreeMap, HashMap }; use former::{ Former, StoragePreform }; + use wtools::Itertools; /// A description of a Value in a command. Used to specify the expected type and provide a hint for the Value. /// @@ -87,7 +86,6 @@ pub( crate ) mod private #[ derive( Debug, Clone, PartialEq, Eq ) ] #[ derive( Former ) ] - // #[ debug ] pub struct Command { /// Command common hint. @@ -100,7 +98,10 @@ pub( crate ) mod private #[ subform_entry( setter = true ) ] pub subjects : Vec< ValueDescription >, /// Hints and types for command options. - pub properties : HashMap< String, ValueDescription >, + pub properties : BTreeMap< CommandName, ValueDescription >, + /// Last inserted property id. + #[ scalar( setter = false ) ] + last_id : usize, /// Map of aliases. // Aliased key -> Original key pub properties_aliases : HashMap< String, String >, @@ -112,6 +113,24 @@ pub( crate ) mod private #[ former( default = Routine::from( Handler::from( || { panic!( "No routine available: A handler function for the command is missing" ) } ) ) ) ] pub routine : Routine, } + + impl Command + { + pub( crate ) fn properties( &self, order : Order ) -> Vec< ( &String, &ValueDescription ) > + { + match order + { + Order::Nature => + { + self.properties.iter().map( | ( key, value ) | ( &key.name, value ) ).collect() + } + Order::Lexicography => + { + self.properties.iter().map( | ( key, value ) | ( &key.name, value ) ).sorted_by_key( | ( k, _ ) | *k ).collect() + } + } + } + } impl< Definition > CommandFormer< Definition > where @@ -201,6 +220,7 @@ pub( crate ) mod private let mut super_former = super_former.unwrap(); let mut properties = super_former.storage.properties.unwrap_or_default(); let property = property.preform(); + let value = ValueDescription { hint : property.hint, @@ -208,7 +228,9 @@ pub( crate ) mod private optional : property.optional, }; debug_assert!( !properties.contains_key( &property.name ), "Property name `{}` is already used for `{:?}`", property.name, properties[ &property.name ] ); - properties.insert( property.name.clone(), value ); + super_former.storage.last_id = Some( super_former.storage.last_id.unwrap_or_default() + 1 ); + let name = CommandName { id : super_former.storage.last_id.unwrap(), name : property.name.clone() }; + properties.insert( name, value ); let mut aliases = super_former.storage.properties_aliases.unwrap_or_default(); debug_assert!( !aliases.contains_key( &property.name ), "Name `{}` is already used for `{}` as alias", property.name, aliases[ &property.name ] ); diff --git a/module/move/wca/src/ca/grammar/dictionary.rs b/module/move/wca/src/ca/grammar/dictionary.rs index a9a79d198a..57ad3f1d71 100644 --- a/module/move/wca/src/ca/grammar/dictionary.rs +++ b/module/move/wca/src/ca/grammar/dictionary.rs @@ -1,10 +1,10 @@ pub( crate ) mod private { use crate::*; - - use { Command }; - use std::collections::HashMap; use former::Former; + use wtools::Itertools; + use std::cmp::Ordering; + use std::collections::BTreeMap; // qqq : `Former` does not handle this situation well @@ -14,14 +14,66 @@ pub( crate ) mod private // #[ derive( Debug, Former ) ] // pub struct Dictionary( HashMap< String, Command > ); + /// Command name with id. + #[ derive( Debug, Default, Clone, Eq ) ] + pub struct CommandName + { + /// id of command. + pub( crate ) id : usize, + /// Name of command. + pub name : String, + } + + impl std::borrow::Borrow< String > for CommandName + { + fn borrow( &self ) -> &String + { + &self.name + } + } + + impl Ord for CommandName + { + fn cmp( &self, other : &Self ) -> Ordering + { + if self.name == other.name + { + Ordering::Equal + } + else + { + self.id.cmp( &other.id ) + } + } + } + + impl PartialEq< Self > for CommandName + { + fn eq( &self, other : &Self ) -> bool + { + self.name.eq( &other.name ) + } + } + + impl PartialOrd for CommandName + { + fn partial_cmp( &self, other : &Self ) -> Option< Ordering > + { + self.id.partial_cmp( &other.id ) + } + } + /// A collection of commands. /// - /// This structure holds a hashmap of commands where each command is mapped to its name. + /// This structure holds a btreemap of commands where each command is mapped to its name. #[ derive( Debug, Default, Former, Clone ) ] pub struct Dictionary { #[ scalar( setter = false, hint = false ) ] - pub( crate ) commands : HashMap< String, Command >, + pub( crate ) commands : BTreeMap< CommandName, Command >, + #[ scalar( setter = false, hint = false ) ] + dictionary_last_id : usize, + pub( crate ) order : Order, } // qqq : IDK how to integrate it into the `CommandsAggregatorFormer` @@ -31,7 +83,9 @@ pub( crate ) mod private pub fn command( mut self, command : Command ) -> Self { let mut commands = self.storage.commands.unwrap_or_default(); - commands.extend([( command.phrase.clone(), command )]); + self.storage.dictionary_last_id = Some( self.storage.dictionary_last_id.unwrap_or_default() + 1 ); + let name = CommandName { id : self.storage.dictionary_last_id.unwrap(), name : command.phrase.clone() }; + commands.insert( name, command ); self.storage.commands = Some( commands ); self @@ -47,7 +101,9 @@ pub( crate ) mod private /// * `command` - The command to be registered. pub fn register( &mut self, command : Command ) -> Option< Command > { - self.commands.insert( command.phrase.clone(), command ) + self.dictionary_last_id += 1; + let name = CommandName { id : self.dictionary_last_id, name : command.phrase.clone() }; + self.commands.insert( name, command ) } /// Retrieves the command with the specified `name` from the `commands` hashmap. @@ -62,10 +118,9 @@ pub( crate ) mod private /// Returns `None` if no command with the specified `name` is found. pub fn command< Name >( &self, name : &Name ) -> Option< &Command > where - String : std::borrow::Borrow< Name >, - Name : std::hash::Hash + Eq, + Name : std::hash::Hash + Eq + Ord + ToString, { - self.commands.get( name ) + self.commands.iter().find( | ( k, _ ) | k.name == name.to_string() ).map( | ( _, v ) | v ) } /// Find commands that match a given name part. @@ -86,6 +141,22 @@ pub( crate ) mod private { self.commands.values().filter( | command | command.phrase.starts_with( name_part.as_ref() ) ).collect() } + + /// asd + pub fn commands( &self ) -> Vec< ( &String, &Command ) > + { + match self.order + { + Order::Nature => + { + self.commands.iter().map( | ( key, value ) | ( &key.name, value ) ).collect() + } + Order::Lexicography => + { + self.commands.iter().map( | ( key, value ) | ( &key.name, value ) ).sorted_by_key( | ( key, _ ) | *key ).collect() + } + } + } } } @@ -94,4 +165,5 @@ pub( crate ) mod private crate::mod_interface! { exposed use Dictionary; + exposed use CommandName; } diff --git a/module/move/wca/src/ca/help.rs b/module/move/wca/src/ca/help.rs index 028a79347e..cc8c897b70 100644 --- a/module/move/wca/src/ca/help.rs +++ b/module/move/wca/src/ca/help.rs @@ -4,14 +4,20 @@ pub( crate ) mod private use ca:: { Command, - Routine, Type, formatter::private::{ HelpFormat, md_generator }, + Routine, + Type, + formatter::private:: + { + HelpFormat, + md_generator + }, + tool::table::format_table, }; use wtools::Itertools; use std::rc::Rc; use error_tools::for_app::anyhow; use former::Former; - use ca::tool::table::format_table; // qqq : for Bohdan : it should transparent mechanist which patch list of commands, not a stand-alone mechanism @@ -53,6 +59,8 @@ pub( crate ) mod private pub description_detailing : LevelOfDetail, /// If enabled - shows complete description of subjects and properties pub with_footer : bool, + /// Order of property and commands. + pub order : Order, } // qqq : for Barsik : make possible to change properties order @@ -90,13 +98,14 @@ pub( crate ) mod private LevelOfDetail::None => "".into(), _ if command.subjects.is_empty() => "".into(), LevelOfDetail::Simple => "< properties >".into(), - LevelOfDetail::Detailed => command.properties.iter().map( |( n, v )| format!( "< {n}:{}{:?} >", if v.optional { "?" } else { "" }, v.kind ) ).collect::< Vec< _ > >().join( " " ), + LevelOfDetail::Detailed => command.properties( dictionary.order ).iter().map( |( n, v )| format!( "< {}:{}{:?} >", if v.optional { "?" } else { "" }, n, v.kind ) ).collect::< Vec< _ > >().join( " " ), }; let footer = if o.with_footer { let full_subjects = command.subjects.iter().map( | subj | format!( "- {} [{}{:?}]", subj.hint, if subj.optional { "?" } else { "" }, subj.kind ) ).join( "\n\t" ); - let full_properties = format_table( command.properties.iter().sorted_by_key( |( name, _ )| *name ).map( |( name, value )| [ name.clone(), format!( "- {} [{}{:?}]", value.hint, if value.optional { "?" } else { "" }, value.kind ) ] ) ).unwrap().replace( '\n', "\n\t" ); + let full_properties = format_table( command.properties( dictionary.order ).into_iter().map( | ( name, value ) | [ name.clone(), format!( "- {} [{}{:?}]", value.hint, if value.optional { "?" } else { "" }, value.kind ) ] ) ).unwrap().replace( '\n', "\n\t" ); + format! ( "{}{}", @@ -130,13 +139,11 @@ pub( crate ) mod private } else { - let rows = dictionary.commands - .iter() - .sorted_by_key( |( name, _ )| *name ) + let rows = dictionary.commands() + .into_iter() .map( |( _, cmd )| cmd ) .map( for_single_command ) .map( | row | [ row.name, row.args, row.hint ] ); - format_table( rows ).unwrap() } } @@ -158,17 +165,17 @@ pub( crate ) mod private impl HelpVariants { /// Generates help commands - pub fn generate( &self, helper : &HelpGeneratorFn, dictionary : &mut Dictionary ) + pub fn generate( &self, helper : &HelpGeneratorFn, dictionary : &mut Dictionary, order : Order ) { match self { HelpVariants::All => { - self.general_help( helper, dictionary ); + self.general_help( helper, dictionary, order ); self.subject_command_help( helper, dictionary ); // self.dot_command_help( helper, dictionary ); }, - HelpVariants::General => self.general_help( helper, dictionary ), + HelpVariants::General => self.general_help( helper, dictionary, order ), HelpVariants::SubjectCommand => self.subject_command_help( helper, dictionary ), _ => unimplemented!() // HelpVariants::DotCommand => self.dot_command_help( helper, dictionary ), @@ -176,7 +183,7 @@ pub( crate ) mod private } // .help - fn general_help( &self, helper : &HelpGeneratorFn, dictionary : &mut Dictionary ) + fn general_help( &self, helper : &HelpGeneratorFn, dictionary : &mut Dictionary, order : Order ) { let phrase = "help".to_string(); @@ -201,22 +208,22 @@ pub( crate ) mod private }; if format == HelpFormat::Markdown { - println!( "Help command\n{text}", text = md_generator( &grammar ) ); + println!( "Help command\n{text}", text = md_generator( &grammar, order ) ); } else { + let options = HelpGeneratorOptions::former() + .command_prefix( "." ) + .description_detailing( LevelOfDetail::Simple ) + .subject_detailing( LevelOfDetail::Simple ) + .property_detailing( LevelOfDetail::Simple ); println! ( "Help command\n\n{text}", text = generator.exec ( &grammar, - HelpGeneratorOptions::former() - .command_prefix( "." ) - .description_detailing( LevelOfDetail::Simple ) - .subject_detailing( LevelOfDetail::Simple ) - .property_detailing( LevelOfDetail::Simple ) - .form() + options.form() ) ); } @@ -266,9 +273,9 @@ pub( crate ) mod private .description_detailing( LevelOfDetail::Detailed ) .subject_detailing( LevelOfDetail::Simple ) .property_detailing( LevelOfDetail::Simple ) - .with_footer( true ) - .form(); - let text = generator.exec( &grammar, args ); + .with_footer( true ); + + let text = generator.exec( &grammar, args.form() ); println!( "Help command\n\n{text}" ); } diff --git a/module/move/wca/src/ca/verifier/verifier.rs b/module/move/wca/src/ca/verifier/verifier.rs index 3ddf6efc8e..8fe25073d3 100644 --- a/module/move/wca/src/ca/verifier/verifier.rs +++ b/module/move/wca/src/ca/verifier/verifier.rs @@ -4,7 +4,7 @@ pub( crate ) mod private use ca::grammar::command::ValueDescription; // use former::Former; - use std::collections::HashMap; + use std::collections::{ BTreeMap, HashMap }; use wtools::{ error, error::Result, err }; use ca::help::private::{ HelpGeneratorOptions, LevelOfDetail, generate_help_content }; @@ -63,7 +63,7 @@ pub( crate ) mod private let sim = dictionary .commands .iter() - .map( |( name, c )| ( jaro.for_str( name, user_input ).nsim(), c ) ) + .map( |( name, c )| ( jaro.for_str( name.name.as_str(), user_input ).nsim(), c ) ) .max_by( |( s1, _ ), ( s2, _ )| s1.total_cmp( s2 ) ); if let Some(( sim, variant )) = sim { @@ -79,7 +79,7 @@ pub( crate ) mod private fn get_count_from_properties ( - properties : &HashMap< String, ValueDescription >, + properties : &BTreeMap< CommandName, ValueDescription >, properties_aliases : &HashMap< String, String >, raw_properties : &HashMap< String, String > ) -> usize diff --git a/module/move/wca/tests/assets/wca_hello_test/src/main.rs b/module/move/wca/tests/assets/wca_hello_test/src/main.rs deleted file mode 100644 index 796622c3e5..0000000000 --- a/module/move/wca/tests/assets/wca_hello_test/src/main.rs +++ /dev/null @@ -1,16 +0,0 @@ -fn main() -{ - use wca::{ Type, VerifiedCommand }; - - let ca = wca::CommandsAggregator::former() - .command( "echo" ) - .hint( "prints all subjects and properties" ) - .subject().hint( "Subject" ).kind( Type::String ).optional( true ).end() - .property( "property" ).hint( "simple property" ).kind( Type::String ).optional( true ).end() - .routine( | o : VerifiedCommand | { println!( "= Args\n{:?}\n\n= Properties\n{:?}\n", o.args, o.props ) } ) - .end() - .perform(); - - let args = std::env::args().skip( 1 ).collect::< Vec< String > >(); - ca.perform( args ).unwrap(); -} \ No newline at end of file diff --git a/module/move/wca/tests/inc/commands_aggregator/help.rs b/module/move/wca/tests/inc/commands_aggregator/help.rs index 3c4f01f6a6..1df2be062e 100644 --- a/module/move/wca/tests/inc/commands_aggregator/help.rs +++ b/module/move/wca/tests/inc/commands_aggregator/help.rs @@ -1,23 +1,8 @@ -use std::fs::File; +use std::fs::{DirBuilder, File}; use std::io::Write; use std::path::Path; use std::process::{Command, Stdio}; -use assert_fs::fixture::PathCopy; -const ASSET_PATH : &str = concat!( env!("CARGO_MANIFEST_DIR"), "/tests/assets/" ); - - -fn arrange( source: &str ) -> assert_fs::TempDir -{ - let root_path = Path::new( env!( "CARGO_MANIFEST_DIR" ) ); - let assets_relative_path = Path::new( ASSET_PATH ); - let assets_path = root_path.join( assets_relative_path ); - - let temp = assert_fs::TempDir::new().unwrap(); - temp.copy_from( assets_path.join( source ), &[ "**" ] ).unwrap(); - - temp -} pub fn start_sync< AP, Args, Arg, P > ( application : AP, @@ -29,14 +14,15 @@ pub fn start_sync< AP, Args, Arg, P > let args = args.into_iter().map( | a | a.as_ref().into() ).collect::< Vec< std::ffi::OsString > >(); let child = Command::new( application ).args( &args ).stdout( Stdio::piped() ).stderr( Stdio::piped() ).current_dir( path ).spawn().unwrap(); let output = child.wait_with_output().unwrap(); - dbg!( &output ); - + String::from_utf8( output.stdout ).unwrap() } #[ test ] fn help_command_with_optional_params() { + let temp = assert_fs::TempDir::new().unwrap(); + let toml = format! ( r#"[package] @@ -47,15 +33,164 @@ edition = "2021" wca = {{path = "{}"}}"#, env!( "CARGO_MANIFEST_DIR" ).replace( "\\", "/" ) ) ; - - let temp = arrange( "wca_hello_test" ); - let mut file = File::create( temp.path().join( "Cargo.toml" ) ).unwrap(); - file.write_all( toml.as_bytes() ).unwrap(); + + let main = r#"use wca::{ Type, VerifiedCommand }; + fn main(){ + let ca = wca::CommandsAggregator::former() + .command( "echo" ) + .hint( "prints all subjects and properties" ) + .subject().hint( "Subject" ).kind( Type::String ).optional( true ).end() + .property( "property" ).hint( "simple property" ).kind( Type::String ).optional( true ).end() + .routine( | o : VerifiedCommand | { println!( "= Args\n{:?}\n\n= Properties\n{:?}\n", o.args, o.props ) } ) + .end() + .perform(); + + let args = std::env::args().skip( 1 ).collect::< Vec< String > >(); + ca.perform( args ).unwrap(); + } + "#; + File::create( temp.path().join( "Cargo.toml" ) ).unwrap().write_all( toml.as_bytes() ).unwrap(); + DirBuilder::new().create( temp.join( "src" ) ).unwrap(); + File::create( temp.path().join( "src" ).join( "main.rs" ) ).unwrap().write_all( main.as_bytes() ).unwrap(); let result = start_sync( "cargo", [ "r", ".help", "echo" ], temp.path() ); - assert_eq! ( "Help command\n\n.echo < subjects > < properties > - prints all subjects and properties\n\nSubjects:\n\t- Subject [?String]\nProperties:\n\tproperty - simple property [?String]\n", result ); } + +#[ test ] +fn help_command_with_nature_order() +{ + let temp = assert_fs::TempDir::new().unwrap(); + + let toml = format! + ( + r#"[package] +name = "wca_hello_test" +version = "0.1.0" +edition = "2021" +[dependencies] +wca = {{path = "{}"}}"#, + env!( "CARGO_MANIFEST_DIR" ).replace( "\\", "/" ) + ) ; + + let main = r#"fn main() + { + use wca::{ Type, VerifiedCommand, Order }; + + let ca = wca::CommandsAggregator::former() + .command( "c" ) + .hint( "c" ) + .property( "c-property" ).kind( Type::String ).optional( true ).end() + .property( "b-property" ).kind( Type::String ).optional( true ).end() + .property( "a-property" ).kind( Type::String ).optional( true ).end() + .routine( | o : VerifiedCommand | { println!("c") } ) + .end() + .command( "b" ) + .hint( "b" ) + .property( "b-property" ).kind( Type::String ).optional( true ).end() + .routine( | o : VerifiedCommand | { println!("b") } ) + .end() + .command( "a" ) + .hint( "a" ) + .property( "a-property" ).kind( Type::String ).optional( true ).end() + .routine( | o : VerifiedCommand | { println!("a") } ) + .end() + .order( Order::Nature ) + + .perform(); + + let args = std::env::args().skip( 1 ).collect::< Vec< String > >(); + ca.perform( args ).unwrap(); + }"#; + + File::create( temp.path().join( "Cargo.toml" ) ).unwrap().write_all( toml.as_bytes() ).unwrap(); + DirBuilder::new().create( temp.join( "src" ) ).unwrap(); + File::create( temp.path().join( "src" ).join( "main.rs" ) ).unwrap().write_all( main.as_bytes() ).unwrap(); + + let result = start_sync( "cargo", [ "r", ".help" ], temp.path() ); + + assert_eq! + ( + "Help command\n\n.c - c\n.b - b\n.a - a\n", + result + ); + + let result = start_sync( "cargo", [ "r", ".help", "c" ], temp.path() ); + + println!( "{result}" ); + + assert_eq! + ( + "Help command\n\n.c - c\n\nProperties:\n\tc-property - [?String]\n\tb-property - [?String]\n\ta-property - [?String]\n", + result + ); +} + +#[ test ] +fn help_command_with_lexicography_order() +{ + let temp = assert_fs::TempDir::new().unwrap(); + + let toml = format! + ( + r#"[package] +name = "wca_hello_test" +version = "0.1.0" +edition = "2021" +[dependencies] +wca = {{path = "{}"}}"#, + env!( "CARGO_MANIFEST_DIR" ).replace( "\\", "/" ) + ) ; + + let main = r#"fn main() + { + use wca::{ Type, VerifiedCommand, Order }; + + let ca = wca::CommandsAggregator::former() + .command( "c" ) + .hint( "c" ) + .property( "c-property" ).kind( Type::String ).optional( true ).end() + .property( "b-property" ).kind( Type::String ).optional( true ).end() + .property( "a-property" ).kind( Type::String ).optional( true ).end() + .routine( | o : VerifiedCommand | { println!("c") } ) + .end() + .command( "b" ) + .hint( "b" ) + .property( "b-property" ).kind( Type::String ).optional( true ).end() + .routine( | o : VerifiedCommand | { println!("b") } ) + .end() + .command( "a" ) + .hint( "a" ) + .property( "a-property" ).kind( Type::String ).optional( true ).end() + .routine( | o : VerifiedCommand | { println!("a") } ) + .end() + .order( Order::Lexicography ) + .perform(); + + let args = std::env::args().skip( 1 ).collect::< Vec< String > >(); + ca.perform( args ).unwrap(); + }"#; + + File::create( temp.path().join( "Cargo.toml" ) ).unwrap().write_all( toml.as_bytes() ).unwrap(); + DirBuilder::new().create( temp.join( "src" ) ).unwrap(); + File::create( temp.path().join( "src" ).join( "main.rs" ) ).unwrap().write_all( main.as_bytes() ).unwrap(); + + let result = start_sync( "cargo", [ "r", ".help" ], temp.path() ); + + assert_eq! + ( + "Help command\n\n.a - a\n.b - b\n.c - c\n", + result + ); + + let result = start_sync( "cargo", [ "r", ".help", "c" ], temp.path() ); + + assert_eq! + ( + "Help command\n\n.c - c\n\nProperties:\n\ta-property - [?String]\n\tb-property - [?String]\n\tc-property - [?String]\n", + result + ); +}