From 775236f926449a47f8bade0b0e2ed8d373171a7f Mon Sep 17 00:00:00 2001 From: Barsik Date: Tue, 12 Mar 2024 18:41:36 +0200 Subject: [PATCH 1/5] wip: refactor help generation --- module/move/wca/src/ca/aggregator.rs | 7 +- module/move/wca/src/ca/executor/converter.rs | 91 -------------------- module/move/wca/src/ca/executor/mod.rs | 4 - module/move/wca/src/ca/help.rs | 59 +++++++++---- 4 files changed, 48 insertions(+), 113 deletions(-) delete mode 100644 module/move/wca/src/ca/executor/converter.rs diff --git a/module/move/wca/src/ca/aggregator.rs b/module/move/wca/src/ca/aggregator.rs index 8ce6348951..beb4c11387 100644 --- a/module/move/wca/src/ca/aggregator.rs +++ b/module/move/wca/src/ca/aggregator.rs @@ -8,7 +8,7 @@ pub( crate ) mod private ProgramParser, Command, grammar::command::private::CommandFormer, - help::{ HelpGeneratorFn, HelpVariants, dot_command }, + help::{ HelpGeneratorFn, HelpGeneratorArgs, HelpVariants, dot_command }, }; use std::collections::HashSet; @@ -20,6 +20,7 @@ pub( crate ) mod private for_app::Error as wError, for_lib::*, }; + use wtools::Itertools; /// Validation errors that can occur in application. #[ derive( Error, Debug ) ] @@ -200,7 +201,7 @@ pub( crate ) mod private /// ``` pub fn help< HelpFunction >( mut self, func : HelpFunction ) -> Self where - HelpFunction : Fn( &Dictionary, Option< &Command > ) -> String + 'static + HelpFunction : Fn( &Dictionary, HelpGeneratorArgs< '_ > ) -> String + 'static { self.container.help_generator = Some( HelpGeneratorFn::new( func ) ); self @@ -244,7 +245,7 @@ pub( crate ) mod private } else { - for help in &ca.help_variants + for help in ca.help_variants.iter().sorted() { help.generate( &ca.help_generator, &mut ca.dictionary ); } diff --git a/module/move/wca/src/ca/executor/converter.rs b/module/move/wca/src/ca/executor/converter.rs deleted file mode 100644 index fe2fbe696b..0000000000 --- a/module/move/wca/src/ca/executor/converter.rs +++ /dev/null @@ -1,91 +0,0 @@ -pub( crate ) mod private -{ - use crate::*; - use former::Former; - use std::collections::HashMap; - use wtools::{ error::Result, err }; - - /// This is the struct that provides a way to convert a `VerifiedCommand` to an `ExecutableCommand_`. - /// - /// The conversion is done by looking up the `Routine` associated with the command in a HashMap of routines. - /// - /// ``` - /// # use wca::{ Command, Type, VerifiedCommand, ExecutorConverter, Routine }; - /// # use std::collections::HashMap; - /// # fn main() -> Result< (), Box< dyn std::error::Error > > { - /// let executor_converter = ExecutorConverter::former() - /// .routine( "command", Routine::new( |( args, props )| Ok( () ) ) ) - /// .form(); - /// - /// let grammar_command = VerifiedCommand - /// { - /// phrase : "command".to_string(), - /// subjects : vec![], - /// properties : HashMap::new(), - /// }; - /// - /// let executable_command = executor_converter.to_command( grammar_command )?; - /// # Ok( () ) } - /// ``` - #[ derive( Debug ) ] - #[ derive( Former ) ] - pub struct ExecutorConverter - { - pub( crate ) routines : HashMap< String, Routine >, - } - - impl ExecutorConverterFormer - { - /// Inserts routine to a routine dictionary - pub fn routine< S >( mut self, phrase : S, routine : Routine ) -> Self - where - S : Into< String >, - Routine : Into< Routine >, - { - let mut routines = self.container.routines.unwrap_or_default(); - - routines.insert( phrase.into(), routine ); - - self.container.routines = Some( routines ); - self - } - } - - impl ExecutorConverter - { - /// Converts raw program to executable - pub fn to_program( &self, raw_program : Program< VerifiedCommand > ) -> Result< Program< ExecutableCommand_ > > - { - let commands = raw_program.commands - .into_iter() - .map( | n | self.to_command( n ) ) - .collect::< Result< Vec< ExecutableCommand_ > > >()?; - - Ok( Program { commands } ) - } - - /// Converts raw command to executable - pub fn to_command( &self, command : VerifiedCommand ) -> Result< ExecutableCommand_ > - { - self.routines - .get( &command.phrase ) - .ok_or_else( || err!( "Can not found routine for command `{}`", command.phrase ) ) - .map - ( - | routine | ExecutableCommand_ - { - subjects : command.subjects, - properties : command.properties, - routine : routine.clone(), - } - ) - } - } -} - -// - -crate::mod_interface! -{ - exposed use ExecutorConverter; -} diff --git a/module/move/wca/src/ca/executor/mod.rs b/module/move/wca/src/ca/executor/mod.rs index 95c0208e12..f0fd5e3997 100644 --- a/module/move/wca/src/ca/executor/mod.rs +++ b/module/move/wca/src/ca/executor/mod.rs @@ -5,12 +5,8 @@ crate::mod_interface! layer executor; /// Represents the state of the program's runtime layer runtime; - // /// Converts from `VerifiedCommand` to `ExecutableCommand_` - // layer converter; /// Container for contexts values layer context; - // /// `ExecutableCommand_` representation - // layer command; /// Command callback representation layer routine; diff --git a/module/move/wca/src/ca/help.rs b/module/move/wca/src/ca/help.rs index 2da464b927..abca2e31bf 100644 --- a/module/move/wca/src/ca/help.rs +++ b/module/move/wca/src/ca/help.rs @@ -10,6 +10,7 @@ pub( crate ) mod private use wtools::Itertools; use std::rc::Rc; use error_tools::for_app::anyhow; + use former::Former; // qqq : for Bohdan : it should transparent mechanist which patch list of commands, not a stand-alone mechanism @@ -50,20 +51,46 @@ pub( crate ) mod private let cmd = Command::former() .hint( "prints all available commands" ) .phrase( "" ) - .subject().hint( "command name" ).kind( Type::String ).optional( true ).end() - // qqq : missing hint - .property( "command_prefix" ).hint( "?" ).kind( Type::String ).optional( true ).end() .routine( routine ) .form(); dictionary.register( cmd ); } - fn generate_help_content( dictionary : &Dictionary, command : Option< &Command > ) -> String + #[ derive( Debug, Default, Copy, Clone, PartialEq, Eq ) ] + pub enum LevelOfDetail { - if let Some( command ) = command + #[ default ] + None, + Simple, + Detailed, + } + + /// Container for arguments passed to a help generator function. + #[ derive( Debug, Former ) ] + pub struct HelpGeneratorArgs< 'a > + { + for_command : Option< &'a Command >, + subject_detailing : LevelOfDetail, + property_detailing : LevelOfDetail, + description_detailing : LevelOfDetail, + with_details : bool, + } + + fn generate_help_content( dictionary : &Dictionary, args : HelpGeneratorArgs< '_ > ) -> String + { + if let Some( command ) = args.for_command { let name = &command.phrase; + let hint = match args.description_detailing + { + LevelOfDetail::None => "", + _ if command.hint.is_empty() && command.long_hint.is_empty() => "", + LevelOfDetail::Simple if !command.hint.is_empty() => command.hint.as_str(), + LevelOfDetail::Detailed if !command.long_hint.is_empty() => command.long_hint.as_str(), + _ if !command.long_hint.is_empty() => command.long_hint.as_str(), + _ if !command.hint.is_empty() => command.hint.as_str(), + }; let hint = if command.long_hint.is_empty() { &command.hint } else { &command.long_hint }; let subjects = if command.subjects.is_empty() { "" } else { " " }; let full_subjects = command.subjects.iter().map( | subj | format!( "- {} [{:?}] {}", subj.hint, subj.kind, if subj.optional { "?" } else { "" } ) ).join( "\n\t" ); @@ -95,7 +122,7 @@ pub( crate ) mod private } /// Available help commands variants - #[ derive( Debug, Hash, PartialEq, Eq ) ] + #[ derive( Debug, Hash, Eq, PartialEq, Ord, PartialOrd ) ] pub enum HelpVariants { /// Make all available variants @@ -158,7 +185,7 @@ pub( crate ) mod private } else { - println!( "Help command\n{text}", text = generator.exec( &grammar, None ) ); + println!( "Help command\n{text}", text = generator.exec( &grammar, HelpGeneratorArgs::former().form() ) ); } } } @@ -200,7 +227,8 @@ pub( crate ) mod private let command = args.get_owned::< String >( 0 ).unwrap(); let cmd = grammar.commands.get( &command ).ok_or_else( || anyhow!( "Can not found help for command `{command}`" ) )?; - let text = generator.exec( &grammar, Some( cmd ) ); + let args = HelpGeneratorArgs::former().for_command( cmd ).form(); + let text = generator.exec( &grammar, args ); println!( "{text}" ); } @@ -274,12 +302,12 @@ pub( crate ) mod private // } } - type HelpFunctionFn = Rc< dyn Fn( &Dictionary, Option< &Command > ) -> String >; + type HelpFunctionFn = Rc< dyn Fn( &Dictionary, HelpGeneratorArgs< '_ > ) -> String >; /// Container for function that generates help string for any command /// /// ``` - /// # use wca::ca::help::HelpGeneratorFn; + /// # use wca::ca::help::{ HelpGeneratorArgs, HelpGeneratorFn }; /// use wca::{ Command, Dictionary }; /// /// fn my_help_generator( grammar : &Dictionary, command : Option< &Command > ) -> String @@ -290,10 +318,10 @@ pub( crate ) mod private /// let help_fn = HelpGeneratorFn::new( my_help_generator ); /// # let grammar = &Dictionary::former().form(); /// - /// help_fn.exec( grammar, None ); + /// help_fn.exec( grammar, HelpGeneratorArgs::former().form() ); /// // or /// # let cmd = Command::former().form(); - /// help_fn.exec( grammar, Some( &cmd ) ); + /// help_fn.exec( grammar, HelpGeneratorArgs::former().for_command( &cmd ).form() ); /// ``` #[ derive( Clone ) ] pub struct HelpGeneratorFn( HelpFunctionFn ); @@ -311,7 +339,7 @@ pub( crate ) mod private /// Wrap a help function pub fn new< HelpFunction >( func : HelpFunction ) -> Self where - HelpFunction : Fn( &Dictionary, Option< &Command > ) -> String + 'static + HelpFunction : Fn( &Dictionary, HelpGeneratorArgs< '_ > ) -> String + 'static { Self( Rc::new( func ) ) } @@ -320,9 +348,9 @@ pub( crate ) mod private impl HelpGeneratorFn { /// Executes the function to generate help content - pub fn exec( &self, dictionary : &Dictionary, command : Option< &Command > ) -> String + pub fn exec( &self, dictionary : &Dictionary, args : HelpGeneratorArgs< '_ > ) -> String { - self.0( dictionary, command ) + self.0( dictionary, args ) } } @@ -340,6 +368,7 @@ pub( crate ) mod private crate::mod_interface! { protected use HelpGeneratorFn; + protected use HelpGeneratorArgs; protected use dot_command; prelude use HelpVariants; } From 05863a01ab97df815a7df63c2e3e3927818936eb Mon Sep 17 00:00:00 2001 From: Barsik-sus Date: Fri, 22 Mar 2024 15:18:51 +0200 Subject: [PATCH 2/5] generate_help_content generates all variants of help content Re-use help for dot help and other variants Re-work help related things in aggregator to be removed after use --- module/move/wca/src/ca/aggregator.rs | 18 ++-- module/move/wca/src/ca/help.rs | 135 ++++++++++++++++----------- 2 files changed, 93 insertions(+), 60 deletions(-) diff --git a/module/move/wca/src/ca/aggregator.rs b/module/move/wca/src/ca/aggregator.rs index beb4c11387..d29111e11c 100644 --- a/module/move/wca/src/ca/aggregator.rs +++ b/module/move/wca/src/ca/aggregator.rs @@ -108,11 +108,12 @@ pub( crate ) mod private #[ default( Executor::former().form() ) ] executor : Executor, - help_generator : HelpGeneratorFn, + help_generator : Option< HelpGeneratorFn >, #[ default( HashSet::from([ HelpVariants::All ]) ) ] help_variants : HashSet< HelpVariants >, - // qqq : for Bohdan : should not have fields help_generator and help_variants + // aaa : for Bohdan : should not have fields help_generator and help_variants // help_generator generateds VerifiedCommand(s) and stop to exist + // aaa : Defaults after formation // #[ default( Verifier::former().form() ) ] #[ default( Verifier ) ] @@ -239,19 +240,22 @@ pub( crate ) mod private { let mut ca = self; - if ca.help_variants.contains( &HelpVariants::All ) + let help_generator = std::mem::take( &mut ca.help_generator ).unwrap_or_default(); + let help_variants = std::mem::take( &mut ca.help_variants ); + + if help_variants.contains( &HelpVariants::All ) { - HelpVariants::All.generate( &ca.help_generator, &mut ca.dictionary ); + HelpVariants::All.generate( &help_generator, &mut ca.dictionary ); } else { - for help in ca.help_variants.iter().sorted() + for help in help_variants.iter().sorted() { - help.generate( &ca.help_generator, &mut ca.dictionary ); + help.generate( &help_generator, &mut ca.dictionary ); } } - dot_command( &mut ca.dictionary ); + dot_command( &help_generator, &mut ca.dictionary ); ca } diff --git a/module/move/wca/src/ca/help.rs b/module/move/wca/src/ca/help.rs index abca2e31bf..e2cff4601e 100644 --- a/module/move/wca/src/ca/help.rs +++ b/module/move/wca/src/ca/help.rs @@ -15,42 +15,25 @@ pub( crate ) mod private // qqq : for Bohdan : it should transparent mechanist which patch list of commands, not a stand-alone mechanism /// Generate `dot` command - pub fn dot_command( dictionary : &mut Dictionary ) + pub fn dot_command( generator : &HelpGeneratorFn, dictionary : &mut Dictionary ) { - let mut available_commands = dictionary.commands.keys().cloned().collect::< Vec< _ > >(); - available_commands.sort(); - - let routine = move | args : Args, props : Props | + let generator = generator.clone(); + let grammar = dictionary.clone(); + let routine = move | props : Props | { let prefix : String = props.get_owned( "command_prefix" ).unwrap(); - if let Some( command ) = args.get_owned::< String >( 0 ) - { - let ac = available_commands - .iter() - .filter( | cmd | cmd.starts_with( &command ) ) - .map( | cmd | format!( "{prefix}{cmd}" ) ) - .collect::< Vec< _ > >(); - if ac.is_empty() - { - return Err( "Have no commands that starts with `{prefix}{command}`" ); - } - else - { - println!( "{}", ac.join( "\n" ) ); - } - } - else - { - println!( "{}", available_commands.iter().map( | cmd | format!( "{prefix}{cmd}" ) ).join( "\n" ) ); - }; + let generator_args = HelpGeneratorArgs::former() + .command_prefix( prefix ) + .form(); - Ok( () ) + println!( "{}", generator.exec( &grammar, generator_args ) ); }; let cmd = Command::former() .hint( "prints all available commands" ) .phrase( "" ) + .property( "command_prefix" ).kind( Type::String ).end() .routine( routine ) .form(); @@ -70,16 +53,18 @@ pub( crate ) mod private #[ derive( Debug, Former ) ] pub struct HelpGeneratorArgs< 'a > { - for_command : Option< &'a Command >, - subject_detailing : LevelOfDetail, - property_detailing : LevelOfDetail, - description_detailing : LevelOfDetail, - with_details : bool, + #[ default( String::new() ) ] + pub command_prefix : String, + pub for_command : Option< &'a Command >, + pub subject_detailing : LevelOfDetail, + pub property_detailing : LevelOfDetail, + pub description_detailing : LevelOfDetail, + pub with_footer : bool, } fn generate_help_content( dictionary : &Dictionary, args : HelpGeneratorArgs< '_ > ) -> String { - if let Some( command ) = args.for_command + let for_single_command = | command : &Command | { let name = &command.phrase; let hint = match args.description_detailing @@ -90,33 +75,59 @@ pub( crate ) mod private LevelOfDetail::Detailed if !command.long_hint.is_empty() => command.long_hint.as_str(), _ if !command.long_hint.is_empty() => command.long_hint.as_str(), _ if !command.hint.is_empty() => command.hint.as_str(), + _ => unreachable!(), }; - let hint = if command.long_hint.is_empty() { &command.hint } else { &command.long_hint }; - let subjects = if command.subjects.is_empty() { "" } else { " " }; - let full_subjects = command.subjects.iter().map( | subj | format!( "- {} [{:?}] {}", subj.hint, subj.kind, if subj.optional { "?" } else { "" } ) ).join( "\n\t" ); - let properties = if command.properties.is_empty() { " " } else { " " }; - let full_properties = command.properties.iter().sorted_by_key( |( name, _ )| *name ).map( |( name, value )| format!( "{name} - {} [{:?}] {}", value.hint, value.kind, if value.optional { "?" } else { "" } ) ).join( "\n\t" ); - - format!( "{name}{subjects}{properties}- {hint}\n{}{}", - if command.subjects.is_empty() { "".to_string() } else { format!( "\nSubjects:\n\t{}", &full_subjects ) }, - if command.properties.is_empty() { "".to_string() } else { format!( "\nProperties:\n\t{}",&full_properties ) }, ) + let subjects = match args.subject_detailing + { + LevelOfDetail::None => "".into(), + _ if command.subjects.is_empty() => "".into(), + LevelOfDetail::Simple => "".into(), + LevelOfDetail::Detailed => command.subjects.iter().map( | v | format!( "<{}{:?}>", if v.optional { "?" } else { "" }, v.kind ) ).collect::< Vec< _ > >().join( " " ), + }; + let properties = match args.property_detailing + { + LevelOfDetail::None => "".into(), + _ if command.subjects.is_empty() => "".into(), + LevelOfDetail::Simple => "".into(), + LevelOfDetail::Detailed => command.properties.iter().map( |( n, v )| format!( "<{n}:{}{:?}>", if v.optional { "?" } else { "" }, v.kind ) ).collect::< Vec< _ > >().join( " " ), + }; + + let footer = if args.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 = command.properties.iter().sorted_by_key( |( name, _ )| *name ).map( |( name, value )| format!( "{name} - {} [{}{:?}]", value.hint, if value.optional { "?" } else { "" }, value.kind ) ).join( "\n\t" ); + format! + ( + "{}{}", + if command.subjects.is_empty() { "".to_string() } else { format!( "\nSubjects:\n\t{}", &full_subjects ) }, + if command.properties.is_empty() { "".to_string() } else { format!( "\nProperties:\n\t{}",&full_properties ) } + ) + } else { "".into() }; + + format! + ( + "{}{name}{}{subjects}{}{properties}{}{hint}{}{footer}", + args.command_prefix, + if !subjects.is_empty() || !properties.is_empty() { " " } else { "" }, + if properties.is_empty() { "" } else { " " }, + if hint.is_empty() { "" } else { " - " }, + if footer.is_empty() { "" } else { "\n" }, + ) + }; + if let Some( command ) = args.for_command + { + for_single_command( command ) } else { dictionary.commands .iter() .sorted_by_key( |( name, _ )| *name ) - .map( |( name, cmd )| - { - let subjects = cmd.subjects.iter().fold( String::new(), | acc, subj | format!( "{acc} <{:?}>", subj.kind ) ); - let properties = if cmd.properties.is_empty() { " " } else { " " }; - let hint = if cmd.hint.is_empty() { &cmd.long_hint } else { &cmd.hint }; - - format!( "{name}{subjects}{properties}- {hint}" ) - }) + .map( |( _, cmd )| cmd ) + .map( for_single_command ) .fold( String::new(), | acc, cmd | { - format!( "{acc}\n{cmd}" ) + format!( "{acc}{}{cmd}", if acc.is_empty() { "" } else { "\n" } ) }) } } @@ -185,7 +196,19 @@ pub( crate ) mod private } else { - println!( "Help command\n{text}", text = generator.exec( &grammar, HelpGeneratorArgs::former().form() ) ); + println! + ( + "Help command\n\n{text}", + text = generator.exec + ( + &grammar, + HelpGeneratorArgs::former() + .description_detailing( LevelOfDetail::Simple ) + .subject_detailing( LevelOfDetail::Simple ) + .property_detailing( LevelOfDetail::Simple ) + .form() + ) + ); } } } @@ -227,10 +250,16 @@ pub( crate ) mod private let command = args.get_owned::< String >( 0 ).unwrap(); let cmd = grammar.commands.get( &command ).ok_or_else( || anyhow!( "Can not found help for command `{command}`" ) )?; - let args = HelpGeneratorArgs::former().for_command( cmd ).form(); + let args = HelpGeneratorArgs::former() + .for_command( cmd ) + .description_detailing( LevelOfDetail::Detailed ) + .subject_detailing( LevelOfDetail::Simple ) + .property_detailing( LevelOfDetail::Simple ) + .with_footer( true ) + .form(); let text = generator.exec( &grammar, args ); - println!( "{text}" ); + println!( "Help command\n\n{text}" ); } }; From 9422ea9c2cbb4fb69146f1ea8c4d30d9c1aaaeb3 Mon Sep 17 00:00:00 2001 From: Barsik-sus Date: Fri, 22 Mar 2024 15:35:51 +0200 Subject: [PATCH 3/5] documentation --- module/move/wca/src/ca/help.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/module/move/wca/src/ca/help.rs b/module/move/wca/src/ca/help.rs index e2cff4601e..a216a040c6 100644 --- a/module/move/wca/src/ca/help.rs +++ b/module/move/wca/src/ca/help.rs @@ -53,12 +53,30 @@ pub( crate ) mod private #[ derive( Debug, Former ) ] pub struct HelpGeneratorArgs< 'a > { + /// Prefix that will be shown before command name #[ default( String::new() ) ] pub command_prefix : String, + /// Show help for the specified command pub for_command : Option< &'a Command >, + /// Reresents how much information to display for the subjects + /// + /// - `None` - nothing + /// - `Simple` - + /// - `Detailed` - each subject with information about it. E.g. `` pub subject_detailing : LevelOfDetail, + /// Reresents how much information to display for the properties + /// + /// - `None` - nothing + /// - `Simple` - + /// - `Detailed` - each property with information about it. E.g. `` pub property_detailing : LevelOfDetail, + /// Reresents how much information to display for the properties + /// + /// - `None` - nothing + /// - `Simple` - short hint + /// - `Detailed` - long hint pub description_detailing : LevelOfDetail, + /// If enabled - shows complete description of subjects and properties pub with_footer : bool, } From f4935fa2e792d119fa09ce5016d3f6fa748b9cde Mon Sep 17 00:00:00 2001 From: Barsik-sus Date: Fri, 22 Mar 2024 16:46:21 +0200 Subject: [PATCH 4/5] remove unsafe --- module/move/wca/Readme.md | 5 -- module/move/wca/examples/wca_fluent.rs | 87 ++++++++++--------- module/move/wca/src/ca/executor/context.rs | 42 ++++----- module/move/wca/src/ca/executor/executor.rs | 8 +- module/move/wca/src/ca/executor/runtime.rs | 4 +- .../tests/inc/commands_aggregator/basic.rs | 3 +- .../wca/tests/inc/commands_aggregator/help.rs | 1 - module/move/wca/tests/inc/executor/command.rs | 8 +- module/move/wca/tests/inc/executor/program.rs | 14 +-- 9 files changed, 87 insertions(+), 85 deletions(-) diff --git a/module/move/wca/Readme.md b/module/move/wca/Readme.md index 5f9708ac25..16415ff49e 100644 --- a/module/move/wca/Readme.md +++ b/module/move/wca/Readme.md @@ -25,10 +25,6 @@ The tool to make CLI ( commands user interface ). It is able to aggregate extern .property( "property" ).hint( "simple property" ).kind( Type::String ).optional( true ).end() .routine( | args : Args, props | { println!( "= Args\n{args:?}\n\n= Properties\n{props:?}\n" ) } ) .end() - .command( "inc" ) - .hint( "This command increments a state number each time it is called consecutively. (E.g. `.inc .inc`)" ) - .routine( | ctx : Context | { let i : &mut i32 = ctx.get_or_default(); println!( "i = {i}" ); *i += 1; } ) - .end() .command( "error" ) .hint( "prints all subjects and properties" ) .subject().hint( "Error message" ).kind( Type::String ).optional( true ).end() @@ -61,4 +57,3 @@ cd wTools cd examples/wca_trivial cargo run ``` - diff --git a/module/move/wca/examples/wca_fluent.rs b/module/move/wca/examples/wca_fluent.rs index 05cde9406f..4de07f8ef4 100644 --- a/module/move/wca/examples/wca_fluent.rs +++ b/module/move/wca/examples/wca_fluent.rs @@ -1,40 +1,47 @@ -//! -//! # Fluent interface example -//! -//! This module introduces a fluent interface implemented via the `wca::CommandsAggregator`, which provides an intuitive method chaining mechanism for creating a command-line interface. -//! -//! The fluent interface and function chaining make it easy to add, update, or modify commands without breaking the application's flow. This design allows for extensibility while keeping the methods structured and clear, making it a good fit for complex CLI applications' needs. -//! - - -use wca::{ Args, Context, Type }; - -fn main() -{ - - let ca = wca::CommandsAggregator::former() - .command( "echo" ) - .hint( "prints all subjects and properties" ) - .subject().kind( Type::String ).optional( true ).end() - .property( "property" ).hint( "simple property" ).kind( Type::String ).optional( true ).end() - .routine( | args : Args, props | { println!( "= Args\n{args:?}\n\n= Properties\n{props:?}\n" ) } ) - .end() - .command( "inc" ) - .hint( "This command increments a state number each time it is called consecutively. (E.g. `.inc .inc`)" ) - .routine( | ctx : Context | { let i : &mut i32 = ctx.get_or_default(); println!( "i = {i}" ); *i += 1; } ) - .end() - .command( "error" ) - .hint( "prints all subjects and properties" ) - .subject().kind( Type::String ).optional( true ).end() - .routine( | args : Args | { println!( "Returns an error" ); Err( format!( "{}", args.get_owned::< String >( 0 ).unwrap_or_default() ) ) } ) - .end() - .command( "exit" ) - .hint( "just exit" ) - .routine( || { println!( "exit" ); std::process::exit( 0 ) } ) - .end() - .perform(); - - let args = std::env::args().skip( 1 ).collect::< Vec< String > >(); - ca.perform( args ).unwrap(); - -} +//! +//! # Fluent interface example +//! +//! This module introduces a fluent interface implemented via the `wca::CommandsAggregator`, which provides an intuitive method chaining mechanism for creating a command-line interface. +//! +//! The fluent interface and function chaining make it easy to add, update, or modify commands without breaking the application's flow. This design allows for extensibility while keeping the methods structured and clear, making it a good fit for complex CLI applications' needs. +//! + + +use wca::{ Args, Context, Type }; +use std::sync::{ Arc, Mutex }; + +fn main() +{ + + let ca = wca::CommandsAggregator::former() + .command( "echo" ) + .hint( "prints all subjects and properties" ) + .subject().kind( Type::String ).optional( true ).end() + .property( "property" ).hint( "simple property" ).kind( Type::String ).optional( true ).end() + .routine( | args : Args, props | { println!( "= Args\n{args:?}\n\n= Properties\n{props:?}\n" ) } ) + .end() + .command( "inc" ) + .hint( "This command increments a state number each time it is called consecutively. (E.g. `.inc .inc`)" ) + .routine( | ctx : Context | + { + let i : Arc< Mutex< i32 > > = ctx.get_or_default(); + let mut i = i.lock().unwrap(); + println!( "i = {}", i ); + *i += 1; + } ) + .end() + .command( "error" ) + .hint( "prints all subjects and properties" ) + .subject().kind( Type::String ).optional( true ).end() + .routine( | args : Args | { println!( "Returns an error" ); Err( format!( "{}", args.get_owned::< String >( 0 ).unwrap_or_default() ) ) } ) + .end() + .command( "exit" ) + .hint( "just exit" ) + .routine( || { println!( "exit" ); std::process::exit( 0 ) } ) + .end() + .perform(); + + let args = std::env::args().skip( 1 ).collect::< Vec< String > >(); + ca.perform( args ).unwrap(); + +} diff --git a/module/move/wca/src/ca/executor/context.rs b/module/move/wca/src/ca/executor/context.rs index 2c738b3b47..e7e5fbf452 100644 --- a/module/move/wca/src/ca/executor/context.rs +++ b/module/move/wca/src/ca/executor/context.rs @@ -50,11 +50,10 @@ pub( crate ) mod private /// Initialize Context with some value pub fn with< T : CloneAny >( mut self, value : T ) -> Self { - if self.container.inner.is_none() - { - self.container.inner = Some( Arc::new( RefCell::new( Map::< dyn CloneAny >::new() ) ) ); - } - self.container.inner.as_ref().map( | inner | inner.borrow_mut().insert( value ) ); + let inner = self.container.inner.unwrap_or_else( || Context::default().inner ); + inner.borrow_mut().insert( value ); + + self.container.inner = Some( inner ); self } } @@ -81,46 +80,39 @@ pub( crate ) mod private self.inner.borrow_mut().remove::< T >() } - // qqq : Bohdan : why unsafe? - /// Return immutable reference on interior object. ! Unsafe ! - pub fn get_ref< T : CloneAny >( &self ) -> Option< &T > - { - unsafe{ self.inner.as_ptr().as_ref()?.get() } - } + // aaa : Bohdan : why unsafe? + // aaa : re-worked. - /// Return mutable reference on interior object. ! Unsafe ! - pub fn get_mut< T : CloneAny >( &self ) -> Option< &mut T > + /// Return immutable reference on interior object. + pub fn get< T : CloneAny + Clone >( &self ) -> Option< T > { - unsafe { self.inner.as_ptr().as_mut()?.get_mut() } + self.inner.borrow().get().cloned() } /// Insert the value if it doesn't exists, or take an existing value and return mutable reference to it - pub fn get_or_insert< T : CloneAny >( &self, value : T ) -> &mut T + pub fn get_or_insert< T : CloneAny + Clone >( &self, value : T ) -> T { - if let Some( value ) = self.get_mut() + if let Some( value ) = self.get() { value } else { self.insert( value ); - self.get_mut().unwrap() + self.get().unwrap() } } /// Insert default value if it doesn't exists, or take an existing value and return mutable reference to it - pub fn get_or_default< T : CloneAny + Default >( &self ) -> &mut T + pub fn get_or_default< T : CloneAny + Default + Clone >( &self ) -> T { self.get_or_insert( T::default() ) } - /// Make a deep clone of the context - // qqq : for Bohdan : why is it deep? how is it deep? - // qqq : how is it useful? Is it? Examples? - pub( crate ) fn deep_clone( &self ) -> Self - { - Self { inner : Arc::new( RefCell::new( ( *self.inner ).borrow_mut().clone() ) ) } - } + // aaa : for Bohdan : why is it deep? how is it deep? + // aaa : how is it useful? Is it? Examples? + // + // aaa : removed } } diff --git a/module/move/wca/src/ca/executor/executor.rs b/module/move/wca/src/ca/executor/executor.rs index 2ae201cced..68717f6762 100644 --- a/module/move/wca/src/ca/executor/executor.rs +++ b/module/move/wca/src/ca/executor/executor.rs @@ -4,6 +4,8 @@ pub( crate ) mod private use ca::executor::runtime::_exec_command; use wtools::error::Result; + use std::sync::Arc; + use std::sync::atomic::Ordering; // aaa : for Bohdan : how is it useful? where is it used? // aaa : `ExecutorType` has been removed @@ -55,10 +57,10 @@ pub( crate ) mod private { while !runtime.is_finished() { - let state = runtime.context.get_or_default::< RuntimeState >(); - state.pos = runtime.pos + 1; + let state = runtime.context.get_or_default::< Arc< RuntimeState > >(); + state.pos.store( runtime.pos + 1, Ordering::Release ); runtime.r#do( &dictionary )?; - runtime.pos = runtime.context.get_ref::< RuntimeState >().unwrap().pos; + runtime.pos = runtime.context.get::< Arc< RuntimeState > >().unwrap().pos.load( Ordering::Relaxed ); } Ok( () ) diff --git a/module/move/wca/src/ca/executor/runtime.rs b/module/move/wca/src/ca/executor/runtime.rs index 8d00d36fe0..d15da797b5 100644 --- a/module/move/wca/src/ca/executor/runtime.rs +++ b/module/move/wca/src/ca/executor/runtime.rs @@ -2,6 +2,8 @@ pub( crate ) mod private { use crate::*; use wtools::{ error::Result, err }; + use std::sync::Arc; + use std::sync::atomic::AtomicUsize; /// State of a program runtime /// @@ -24,7 +26,7 @@ pub( crate ) mod private pub struct RuntimeState { /// current execution position that can be changed by user - pub pos : usize, + pub pos : Arc< AtomicUsize >, } // qqq : for Bohdan : why? how is it useful? is it? diff --git a/module/move/wca/tests/inc/commands_aggregator/basic.rs b/module/move/wca/tests/inc/commands_aggregator/basic.rs index 0ffe9105ff..46332cd15b 100644 --- a/module/move/wca/tests/inc/commands_aggregator/basic.rs +++ b/module/move/wca/tests/inc/commands_aggregator/basic.rs @@ -69,7 +69,8 @@ tests_impls! .perform(); a_id!( (), ca.perform( "." ).unwrap() ); - a_id!( (), ca.perform( ".cmd." ).unwrap() ); + // qqq : this use case is disabled + // a_id!( (), ca.perform( ".cmd." ).unwrap() ); a_true!( ca.perform( ".c." ).is_err() ); } diff --git a/module/move/wca/tests/inc/commands_aggregator/help.rs b/module/move/wca/tests/inc/commands_aggregator/help.rs index dd72f912ab..cbc3e96997 100644 --- a/module/move/wca/tests/inc/commands_aggregator/help.rs +++ b/module/move/wca/tests/inc/commands_aggregator/help.rs @@ -57,4 +57,3 @@ wca = {{path = "{}"}}"#, result ); } - diff --git a/module/move/wca/tests/inc/executor/command.rs b/module/move/wca/tests/inc/executor/command.rs index 42033a111d..4d4ade6208 100644 --- a/module/move/wca/tests/inc/executor/command.rs +++ b/module/move/wca/tests/inc/executor/command.rs @@ -111,6 +111,8 @@ tests_impls! fn with_context() { + use std::sync::{ Arc, Mutex }; + // init parser let parser = Parser::former().form(); @@ -126,16 +128,16 @@ tests_impls! ( | ctx : Context | ctx - .get_ref() + .get() .ok_or_else( || "Have no value" ) - .and_then( | &x : &i32 | if x != 1 { Err( "x not eq 1" ) } else { Ok( () ) } ) + .and_then( | x : Arc< Mutex< i32 > > | if *x.lock().unwrap() != 1 { Err( "x not eq 1" ) } else { Ok( () ) } ) ) .form() ) .form(); let verifier = Verifier; let mut ctx = wca::Context::default(); - ctx.insert( 1 ); + ctx.insert( Arc::new( Mutex::new( 1 ) ) ); // init executor let executor = Executor::former() .context( ctx ) diff --git a/module/move/wca/tests/inc/executor/program.rs b/module/move/wca/tests/inc/executor/program.rs index c3666127db..803060386e 100644 --- a/module/move/wca/tests/inc/executor/program.rs +++ b/module/move/wca/tests/inc/executor/program.rs @@ -36,6 +36,7 @@ tests_impls! fn with_context() { + use std::sync::{ Arc, Mutex }; use wtools::error::for_app::Error; // init parser @@ -53,9 +54,9 @@ tests_impls! ( | ctx : Context | ctx - .get_mut() + .get() .ok_or_else( || "Have no value" ) - .and_then( | x : &mut i32 | { *x += 1; Ok( () ) } ) + .and_then( | x : Arc< Mutex< i32 > > | { *x.lock().unwrap() += 1; Ok( () ) } ) ) .form() ) @@ -70,15 +71,16 @@ tests_impls! ( | ctx : Context, args : Args | ctx - .get_ref() + .get() .ok_or_else( || "Have no value".to_string() ) .and_then ( - | &x : &i32 | + | x : Arc< Mutex< i32 > > | { + let x = x.lock().unwrap(); let y : i32 = args.get( 0 ).ok_or_else( || "Missing subject".to_string() ).unwrap().to_owned().into(); - if dbg!( x ) != y { Err( format!( "{} not eq {}", x, y ) ) } else { Ok( () ) } + if dbg!( *x ) != y { Err( format!( "{} not eq {}", x, y ) ) } else { Ok( () ) } } ) ) @@ -89,7 +91,7 @@ tests_impls! // starts with 0 let mut ctx = wca::Context::default(); - ctx.insert( 0 ); + ctx.insert( Arc::new( Mutex::new( 0 ) ) ); // init simple executor let executor = Executor::former() .context( ctx ) From 8999fd265ec27c38969cfcd505f5fc3ac1c3bc9a Mon Sep 17 00:00:00 2001 From: Barsik Date: Fri, 22 Mar 2024 21:13:39 +0200 Subject: [PATCH 5/5] Update help command output in test The help command output in the test file commands_aggregator/help.rs has been revised. The updated test now expects an output that starts with "Help command", aligning it with the new format changes. --- module/move/wca/tests/inc/commands_aggregator/help.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/move/wca/tests/inc/commands_aggregator/help.rs b/module/move/wca/tests/inc/commands_aggregator/help.rs index f5b58c892a..4d46399274 100644 --- a/module/move/wca/tests/inc/commands_aggregator/help.rs +++ b/module/move/wca/tests/inc/commands_aggregator/help.rs @@ -53,7 +53,7 @@ wca = {{path = "{}"}}"#, assert_eq! ( - "echo < subjects > < properties > - prints all subjects and properties\n\nSubjects:\n\t- Subject [String] ?\nProperties:\n\tproperty - simple property [String] ?\n", + "Help command\n\necho < subjects > < properties > - prints all subjects and properties\n\nSubjects:\n\t- Subject [?String]\nProperties:\n\tproperty - simple property [?String]\n", result ); }