#[ allow( clippy ::std_instead_of_alloc, clippy ::std_instead_of_core ) ]
mod private
{
use crate :: *;
use ca ::
{
Type,
Order,
formatter ::
{
HelpFormat,
md_generator
},
tool ::table ::format_table,
};
use verifier ::VerifiedCommand;
use grammar :: { Command, Dictionary };
use executor ::Routine;
use iter_tools ::Itertools;
use std ::rc ::Rc;
use error_tools ::untyped ::format_err;
use former ::Former;
#[ derive( Debug, Default, Copy, Clone, PartialEq, Eq ) ]
pub enum LevelOfDetail
{
#[ default ]
None,
Simple,
Detailed,
}
#[ derive( Debug, Former ) ]
pub struct HelpGeneratorOptions< 'a >
{
#[ former( default = String ::new() ) ]
pub command_prefix: String,
pub for_commands: Vec< &'a Command >,
pub subject_detailing: LevelOfDetail,
pub property_detailing: LevelOfDetail,
pub description_detailing: LevelOfDetail,
pub with_footer: bool,
#[ former( default = Order ::default() ) ]
pub order: Order,
}
#[ must_use ]
#[ allow( clippy ::match_same_arms ) ]
pub fn generate_help_content( dictionary: &Dictionary, o: HelpGeneratorOptions< '_ > ) -> String
{
struct Row
{
name: String,
args: String,
hint: String,
footer: String,
}
let for_single_command = | command: &Command |
{
let name = &command.phrase;
let hint = match o.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(),
_ => unreachable!(),
};
let subjects = match o.subject_detailing
{
LevelOfDetail ::None => String ::new(),
_ if command.subjects.is_empty() => String ::new(),
LevelOfDetail ::Simple => "< subjects >".into(),
LevelOfDetail ::Detailed => command.subjects.iter().map( | v |
{
format!( "< {}{:?} >", if v.optional { "?" } else { "" }, v.kind )
}).collect :: < Vec< _ > >().join( " " ),
};
let properties = match o.property_detailing
{
LevelOfDetail ::None => String ::new(),
_ if command.subjects.is_empty() => String ::new(),
LevelOfDetail ::Simple => "< properties >".into(),
LevelOfDetail ::Detailed => command.properties( o.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( o.order ).into_iter().map( | ( name, value ) |
{
[ name.clone(), format!( "- {} [{}{:?}]", value.hint, if value.optional { "?" } else { "" }, value.kind ) ]
})).unwrap().replace( '\n', "\n\t" );
format!
(
"{}{}",
if command.subjects.is_empty()
{ String ::new() } else { format!( "\nSubjects: \n\t{}", &full_subjects ) },
if command.properties.is_empty()
{ String ::new() } else { format!( "\nProperties: \n\t{}",&full_properties ) }
)
} else { String ::new() };
Row
{
name: format!( "{}{name}", o.command_prefix ),
args: format!( "{subjects}{}{properties}", if !subjects.is_empty() || !properties.is_empty() { " " } else { "" } ),
hint: format!( "{}{hint}", if hint.is_empty() { "" } else { "- " } ),
footer,
}
};
if o.for_commands.len() == 1 || !o.for_commands.is_empty() && !o.with_footer
{
o.for_commands.into_iter().map( | command |
{
let row = for_single_command( command );
format!
(
"{}{}{}",
format_table( [ [ row.name, row.args, row.hint ] ] ).unwrap(),
if row.footer.is_empty()
{ "" } else { "\n" },
row.footer
)
})
.join( "\n" )
}
else
{
let commands_iter = match o.order
{
Order ::Nature => dictionary.commands.iter().collect :: < Vec< _ > >(),
Order ::Lexicography => dictionary.commands.iter().sorted_by_key( | ( key, _ ) | *key ).collect :: < Vec< _ > >(),
};
let rows = commands_iter
.into_iter()
.map( | ( _, cmd ) | cmd )
.map( for_single_command )
.map( | row | [ row.name, row.args, row.hint ] );
format_table( rows ).unwrap()
}
}
#[ derive( Debug, Hash, Eq, PartialEq, Ord, PartialOrd ) ]
pub enum HelpVariants
{
All,
General,
SubjectCommand,
DotCommand,
}
impl HelpVariants
{
#[ allow( clippy ::match_wildcard_for_single_variants ) ]
pub fn generate( &self, helper: &HelpGeneratorFn, dictionary: &mut Dictionary, order: Order )
{
match self
{
HelpVariants ::All =>
{
self.unified_help( helper, dictionary, order );
},
HelpVariants ::General => self.general_help( helper, dictionary, order ),
HelpVariants ::SubjectCommand => self.subject_command_help( helper, dictionary, order ),
_ => unimplemented!()
}
}
#[ allow( clippy ::unused_self ) ]
fn unified_help( &self, helper: &HelpGeneratorFn, dictionary: &mut Dictionary, order: Order )
{
let phrase = "help".to_string();
let grammar = dictionary.clone();
let generator = helper.clone();
let routine = move | o: VerifiedCommand |
{
if o.args.0.is_empty()
{
let format_prop: String = o.props.get_owned( "format" ).unwrap_or_default();
let format = match format_prop.as_str()
{
"md" | "markdown" => HelpFormat ::Markdown,
_ => HelpFormat ::Another,
};
if format == HelpFormat ::Markdown
{
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 )
.order( order );
println!
(
"Help command\n\n{text}",
text = generator.exec
(
&grammar,
options.form()
)
);
}
}
else
{
let command = o.args.get_owned :: < String >( 0 ).unwrap();
let cmd = grammar.commands
.get( &command )
.ok_or_else( || format_err!( "Can not found help for command `{command}`" ) )?;
let args = HelpGeneratorOptions ::former()
.command_prefix( "." )
.for_commands( [ cmd ] )
.description_detailing( LevelOfDetail ::Detailed )
.subject_detailing( LevelOfDetail ::Simple )
.property_detailing( LevelOfDetail ::Simple )
.with_footer( true )
.order( order );
let text = generator.exec( &grammar, args.form() );
println!( "Help command\n\n{text}" );
}
Ok :: < _, error_tools ::untyped ::Error >( () )
};
let help = Command ::former()
.hint( "prints full information about a specified command" )
.subject()
.hint( "command name" )
.kind( Type ::String )
.optional( true )
.end()
.property( "format" )
.hint( "help generates in format witch you write" )
.kind( Type ::String )
.optional( true )
.end()
.phrase( &phrase )
.routine( routine )
.form();
dictionary.register( help );
}
#[ allow( clippy ::unused_self ) ]
fn general_help( &self, helper: &HelpGeneratorFn, dictionary: &mut Dictionary, order: Order )
{
let phrase = "help".to_string();
let grammar = dictionary.clone();
let generator = helper.clone();
let moved_phrase = phrase.clone();
let routine = move | o: VerifiedCommand |
{
let subject_help = grammar.command( &moved_phrase );
match &subject_help
{
Some( Command { routine: Routine ::WithoutContext( help ), .. } )
if !o.args.0.is_empty() => help( o )?,
_ =>
{
let format_prop: String = o.props.get_owned( "format" ).unwrap_or_default();
let format = match format_prop.as_str()
{
"md" | "markdown" => HelpFormat ::Markdown,
_ => HelpFormat ::Another,
};
if format == HelpFormat ::Markdown
{
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 )
.order( order );
println!
(
"Help command\n\n{text}",
text = generator.exec
(
&grammar,
options.form()
)
);
}
}
}
Ok :: < _, error_tools ::untyped ::Error >( () )
};
let help = Command ::former()
.hint( "prints information about existing commands" )
.property( "format" )
.hint( "help generates in format witch you write" )
.kind( Type ::String )
.optional( true )
.end()
.phrase( &phrase )
.routine( routine )
.form();
dictionary.register( help );
}
#[ allow( clippy ::unused_self ) ]
fn subject_command_help( &self, helper: &HelpGeneratorFn, dictionary: &mut Dictionary, order: Order )
{
let phrase = "help".to_string();
let grammar = dictionary.clone();
let generator = helper.clone();
let moved_phrase = phrase.clone();
let routine = move | o: VerifiedCommand |
{
let full_help = grammar.command( &moved_phrase );
match &full_help
{
Some( Command { routine: Routine ::WithoutContext( help ), .. } )
if o.args.0.is_empty() => help( o )?,
_ =>
{
let command = o.args.get_owned :: < String >( 0 ).unwrap();
let cmd = grammar.commands
.get( &command )
.ok_or_else( || format_err!( "Can not found help for command `{command}`" ) )?;
let args = HelpGeneratorOptions ::former()
.command_prefix( "." )
.for_commands( [ cmd ] )
.description_detailing( LevelOfDetail ::Detailed )
.subject_detailing( LevelOfDetail ::Simple )
.property_detailing( LevelOfDetail ::Simple )
.with_footer( true )
.order( order );
let text = generator.exec( &grammar, args.form() );
println!( "Help command\n\n{text}" );
}
}
Ok :: < _, error_tools ::untyped ::Error >( () )
};
let help = Command ::former()
.hint( "prints full information about a specified command" )
.subject()
.hint( "command name" )
.kind( Type ::String )
.optional( true )
.end()
.property( "format" )
.hint( "help generates in format witch you write" )
.kind( Type ::String )
.optional( true )
.end()
.phrase( &phrase )
.routine( routine )
.form();
dictionary.register( help );
}
}
type HelpFunctionFn = Rc< dyn Fn( &Dictionary, HelpGeneratorOptions< '_ > ) -> String >;
#[ derive( Clone ) ]
pub struct HelpGeneratorFn( HelpFunctionFn );
impl Default for HelpGeneratorFn
{
fn default() -> Self
{
Self( Rc ::new( generate_help_content ) )
}
}
impl HelpGeneratorFn
{
pub fn new< HelpFunction >( func: HelpFunction ) -> Self
where
HelpFunction: Fn( &Dictionary, HelpGeneratorOptions< '_ > ) -> String + 'static
{
Self( Rc ::new( func ) )
}
}
impl HelpGeneratorFn
{
#[ must_use ]
pub fn exec( &self, dictionary: &Dictionary, args: HelpGeneratorOptions< '_ > ) -> String
{
self.0( dictionary, args )
}
}
impl core ::fmt ::Debug for HelpGeneratorFn
{
fn fmt( &self, f: &mut core ::fmt ::Formatter< '_ > ) -> core ::fmt ::Result
{
f.write_str( "HelpGenerator" )
}
}
}
crate ::mod_interface!
{
own use HelpGeneratorFn;
own use HelpGeneratorOptions;
own use LevelOfDetail;
own use generate_help_content;
prelude use HelpVariants;
}