diff --git a/module/move/wca/Readme.md b/module/move/wca/Readme.md index dfdb626510..ecbad92af5 100644 --- a/module/move/wca/Readme.md +++ b/module/move/wca/Readme.md @@ -27,10 +27,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() @@ -63,4 +59,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 c7f4b177e2..4de07f8ef4 100644 --- a/module/move/wca/examples/wca_fluent.rs +++ b/module/move/wca/examples/wca_fluent.rs @@ -8,6 +8,7 @@ use wca::{ Args, Context, Type }; +use std::sync::{ Arc, Mutex }; fn main() { @@ -21,7 +22,13 @@ fn main() .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; } ) + .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" ) diff --git a/module/move/wca/src/ca/aggregator.rs b/module/move/wca/src/ca/aggregator.rs index 62fa0ba4e6..63fe9d3a01 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/executor/context.rs b/module/move/wca/src/ca/executor/context.rs index 82d973d26a..ab0b8921ce 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.storage.inner.is_none() - { - self.storage.inner = Some( Arc::new( RefCell::new( Map::< dyn CloneAny >::new() ) ) ); - } - self.storage.inner.as_ref().map( | inner | inner.borrow_mut().insert( value ) ); + let inner = self.storage.inner.unwrap_or_else( || Context::default().inner ); + inner.borrow_mut().insert( value ); + + self.storage.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 4f3c3ad4a6..80ac92f2f6 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/src/ca/help.rs b/module/move/wca/src/ca/help.rs index 58ff9268a6..95a5a04d5b 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,17 +53,37 @@ 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, + /// 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, } // qqq : for Barsik : make possible to change properties order 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 @@ -91,33 +94,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 { " < subjects > " }; - 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 { " < properties > " }; - 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 => "< subjects >".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 => "< properties >".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 { " < properties > " }; - 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" } ) }) } } @@ -186,7 +215,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() + ) + ); } } } @@ -228,10 +269,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}" ); } }; diff --git a/module/move/wca/tests/inc/commands_aggregator/basic.rs b/module/move/wca/tests/inc/commands_aggregator/basic.rs index 2a47a7f4d4..f01965a6ed 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 a479d37d2c..f5b58c892a 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 )