From 9ee657c2b618b72638e282c5002faf2e56622355 Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Thu, 2 Aug 2018 15:04:18 +0200 Subject: [PATCH 1/8] remove all clap-related code --- examples/demo.rs | 49 ---- examples/main.rs | 65 ++++-- src/{man => }/author.rs | 0 src/{man => }/environment.rs | 0 src/{man => }/flag.rs | 0 src/lib.rs | 425 ++++++++++++++++++++++++++++------- src/{man => }/mod.rs | 0 src/{man => }/option.rs | 0 8 files changed, 391 insertions(+), 148 deletions(-) delete mode 100644 examples/demo.rs rename src/{man => }/author.rs (100%) rename src/{man => }/environment.rs (100%) rename src/{man => }/flag.rs (100%) rename src/{man => }/mod.rs (100%) rename src/{man => }/option.rs (100%) diff --git a/examples/demo.rs b/examples/demo.rs deleted file mode 100644 index e3306eb..0000000 --- a/examples/demo.rs +++ /dev/null @@ -1,49 +0,0 @@ -extern crate man; - -use man::Man; - -fn main() { - let msg = Man::new("auth-service") - .description("authorize & authenticate members") - .argument("path".into()) - .environment( - "PORT".into(), - None, - Some("The network port to listen to.".into()), - ) - .flag( - Some("-h".into()), - Some("--help".into()), - Some("Prints help information.".into()), - ) - .flag( - Some("-V".into()), - Some("--version".into()), - Some("Prints version information.".into()), - ) - .flag( - Some("-v".into()), - Some("--verbosity".into()), - Some("Pass multiple times to print more information.".into()), - ) - .option( - Some("-a".into()), - Some("--address".into()), - Some("The network address to listen to.".into()), - "address".into(), - Some("127.0.0.1".into()), - ) - .option( - Some("-p".into()), - Some("--port".into()), - Some("The network port to listen to.".into()), - "port".into(), - None, - ) - .author("Alice Person", Some("alice@person.com".into())) - .author("Bob Human", Some("bob@human.com".into())) - .render(); - // .option(Some("-o"), Some("--output"), "output", None, "Output file"); - - println!("{}", msg); -} diff --git a/examples/main.rs b/examples/main.rs index bfb3b69..e3306eb 100644 --- a/examples/main.rs +++ b/examples/main.rs @@ -1,28 +1,49 @@ -extern crate clap; extern crate man; -use clap::{App, AppSettings, Arg, Man, SubCommand}; -use man::Manual; +use man::Man; fn main() { - let a = App::new("testapp") - .about("Pointless application") - .setting(AppSettings::SubcommandRequiredElseHelp) - .author("Katharina Fey ") - // .author("Yosh Wuyts , - long: Option, - descr: Option, -} - -#[derive(Debug)] -pub struct Manual { - /// Optionally children of this app - children: Vec<(String, Manual)>, - /// Application name - name: Option, - /// Type description +/// Man page struct. +#[derive(Debug, Clone)] +pub struct Man { + name: String, description: Option, - /// Type authors - authors: Option, - /// Type flags - flags: Vec, - /// Type options - options: Vec, + authors: Vec, + flags: Vec, + options: Vec, + environment: Vec, + arguments: Vec, } -impl<'a, 'b, 'c> From<&'c Arg<'a, 'b>> for ManualArg { - fn from(s: &'c Arg) -> Self { - ManualArg { - short: s.short, - long: match s.long { - Some(s) => Some(s.into()), - _ => None, - }, - descr: match s.help { - Some(s) => Some(s.into()), - _ => None, - }, +impl Man { + /// Create a new instance. + pub fn new(name: &str) -> Self { + Self { + name: name.into(), + description: None, + authors: vec![], + flags: vec![], + options: vec![], + arguments: vec![], + environment: vec![], } } + + /// Add a description. + pub fn description(mut self, desc: &str) -> Self { + let desc = desc.into(); + self.description = Some(desc); + self + } + + /// Add an author. + pub fn author( + mut self, + name: impl AsRef, + email: Option, + ) -> Self { + self.authors.push(Author { + name: name.as_ref().to_owned(), + email, + }); + self + } + + /// Add an environment variable. + pub fn environment( + mut self, + name: String, + default: Option, + description: Option, + ) -> Self { + self.environment.push(Env { + name, + default, + description, + }); + self + } + + /// Add an flag. + pub fn flag( + mut self, + short: Option, + long: Option, + description: Option, + ) -> Self { + self.flags.push(Flag { + short, + long, + description, + }); + self + } + + /// Add an option. + pub fn option( + mut self, + short: Option, + long: Option, + description: Option, + argument: String, + default: Option, + ) -> Self { + self.options.push(Opt { + short, + long, + description, + argument, + default, + }); + self + } + + /// Add a positional argument. The items are displayed in the order they're + /// pushed. + // TODO: make this accept argument vecs / optional args too. `arg...`, `arg?` + pub fn argument(mut self, arg: String) -> Self { + self.arguments.push(arg); + self + } + + pub fn render(self) -> String { + let man_num = 1; + let mut page = Roff::new(&self.name, man_num); + page = description(page, &self.name, &self.description); + page = synopsis( + page, + &self.name, + &self.flags, + &self.options, + &self.arguments, + ); + page = flags(page, &self.flags); + page = options(page, &self.options); + page = environment(page, &self.environment); + page = exit_status(page); + page = authors(page, &self.authors); + page.render() + } } -impl Manual { - fn new() -> Self { - Manual { - children: Vec::new(), - name: None, - description: None, - authors: None, - flags: Vec::new(), - options: Vec::new(), +/// Create a `NAME` section. +/// +/// ## Formatting +/// ```txt +/// NAME +/// mycmd - brief description of the application +/// ``` +fn description(page: Roff, name: &str, desc: &Option) -> Roff { + let desc = match desc { + Some(ref desc) => format!("{} - {}", name, desc), + None => name.to_owned(), + }; + + page.section("NAME", &[desc]) +} + +/// Create a `SYNOPSIS` section. +fn synopsis( + page: Roff, + name: &str, + flags: &[Flag], + options: &[Opt], + args: &[String], +) -> Roff { + let flags = match flags.len() { + 0 => "".into(), + _ => " [FLAGS]".into(), + }; + let options = match options.len() { + 0 => "".into(), + _ => " [OPTIONS]".into(), + }; + + let mut msg = vec![]; + msg.push(bold(name)); + msg.push(flags); + msg.push(options); + + for arg in args { + msg.push(format!(" {}", arg)); + } + + page.section("SYNOPSIS", &msg) +} + +/// Create a `AUTHOR` or `AUTHORS` section. +/// +/// ## Formatting +/// ```txt +/// AUTHORS +/// Alice Person +/// Bob Human +/// ``` +fn authors(page: Roff, authors: &[Author]) -> Roff { + let title = match authors.len() { + 0 => return page, + 1 => "AUTHOR", + _ => "AUTHORS", + }; + + let last = authors.len() - 1; + let mut auth_values = vec![]; + auth_values.push(init_list()); + for (index, author) in authors.iter().enumerate() { + auth_values.push(author.name.to_owned()); + + if let Some(ref email) = author.email { + auth_values.push(format!(" <{}>", email)) + }; + + if index != last { + auth_values.push(format!("\n")); } } - // TODO: Make this less awful - fn add_empty_child(&mut self, name: &str) -> &mut Manual { - self.children.push((name.into(), Manual::new())); - let (_, ref mut manual) = self.children.last_mut().unwrap(); - manual + page.section(title, &auth_values) +} + +/// Create a `FLAGS` section. +/// +/// ## Formatting +/// ```txt +/// FLAGS +/// ``` +fn flags(page: Roff, flags: &[Flag]) -> Roff { + if flags.is_empty() { + return page; } - fn recursive(manual: &mut Manual, app: &App) { - manual.name = app.name.clone().into(); - manual.description = app.about.map(Into::into); - manual.authors = app.author.map(Into::into); + let last = flags.len() - 1; + let mut arr: Vec = vec![]; + for (index, flag) in flags.iter().enumerate() { + let mut args: Vec = vec![]; + if let Some(ref short) = flag.short { + args.push(bold(&short)); + } + if let Some(ref long) = flag.long { + if !args.is_empty() { + args.push(", ".to_string()); + } + args.push(bold(&long)); + } + let desc = match flag.description { + Some(ref desc) => desc.to_string(), + None => "".to_string(), + }; + arr.push(list(&args, &[desc])); - let (flags, options): (Vec, Vec) = app - .args - .iter() - .fold((Vec::new(), Vec::new()), |(mut f, mut o), i: &Arg| { - if i.is_set(ArgSettings::TakesValue) { - f.push(i.into()); - } else { - o.push(i.into()); - } + if index != last { + arr.push(format!("\n\n")); + } + } + page.section("FLAGS", &arr) +} + +/// Create a `OPTIONS` section. +/// +/// ## Formatting +/// ```txt +/// OPTIONS +/// ``` +fn options(page: Roff, options: &[Opt]) -> Roff { + if options.is_empty() { + return page; + } - (f, o) - }); + let last = options.len() - 1; + let mut arr: Vec = vec![]; + for (index, opt) in options.iter().enumerate() { + let mut args: Vec = vec![]; + if let Some(ref short) = opt.short { + args.push(bold(&short)); + } + if let Some(ref long) = opt.long { + if !args.is_empty() { + args.push(", ".to_string()); + } + args.push(bold(&long)); + } + args.push("=".into()); + args.push(italic(&opt.argument)); + if let Some(ref default) = opt.default { + if !args.is_empty() { + args.push(" ".to_string()); + } + args.push("[".into()); + args.push("default:".into()); + args.push(" ".into()); + args.push(italic(&default)); + args.push("]".into()); + } + let desc = match opt.description { + Some(ref desc) => desc.to_string(), + None => "".to_string(), + }; + arr.push(list(&args, &[desc])); - manual.flags = flags; - manual.options = options; + if index != last { + arr.push(format!("\n\n")); + } + } + page.section("OPTIONS", &arr) +} - app.subcommands.iter().for_each(|app| { - let _inner_name: String = app.name.clone(); - let mut inner = manual.add_empty_child(&app.name); - Manual::recursive(&mut inner, app); - }); +/// Create a `ENVIRONMENT` section. +/// +/// ## Formatting +/// ```txt +/// ENVIRONMENT +/// ``` +fn environment(page: Roff, environment: &[Env]) -> Roff { + if environment.is_empty() { + return page; } - pub fn from_clap<'a, 'b>(app: &App<'a, 'b>) -> Manual { - let mut man = Manual::new(); - Manual::recursive(&mut man, app); - man + let last = environment.len() - 1; + let mut arr: Vec = vec![]; + for (index, env) in environment.iter().enumerate() { + let mut args: Vec = vec![]; + args.push(bold(&env.name)); + if let Some(ref default) = env.default { + if !args.is_empty() { + args.push(" ".to_string()); + } + args.push("[".into()); + args.push("default:".into()); + args.push(" ".into()); + args.push(italic(&default)); + args.push("]".into()); + } + let desc = match env.description { + Some(ref desc) => desc.to_string(), + None => "".to_string(), + }; + arr.push(list(&args, &[desc])); + + if index != last { + arr.push(format!("\n\n")); + } } + page.section("ENVIRONMENT", &arr) +} + +/// Create a `EXIT STATUS` section. +/// +/// ## Implementation Note +/// This currently only returns the status code `0`, and takes no arguments. We +/// should let it take arguments. +/// +/// ## Formatting +/// ```txt +/// EXIT STATUS +/// 0 Successful program execution +/// +/// 1 Usage, syntax or configuration file error +/// +/// 2 Optional error +/// ``` +fn exit_status(page: Roff) -> Roff { + page.section( + "EXIT STATUS", + &[list(&[bold("0")], &["Successful program execution."])], + ) +} + +// NOTE(yw): This code was taken from the npm-install(1) command. The location +// on your system may vary. In all honesty I just copy-pasted this. We should +// probably port this to troff-rs at some point. +// +// ```sh +// $ less /usr/share/man/man1/npm-install.1 +// ``` +fn init_list() -> String { + format!(".P\n.RS 2\n.nf\n") } diff --git a/src/man/mod.rs b/src/mod.rs similarity index 100% rename from src/man/mod.rs rename to src/mod.rs diff --git a/src/man/option.rs b/src/option.rs similarity index 100% rename from src/man/option.rs rename to src/option.rs From 44b59b430b9b9647db2205afe3dcb4a88c861287 Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Fri, 3 Aug 2018 12:59:41 +0200 Subject: [PATCH 2/8] builder pattern all the way through --- src/author.rs | 16 ++ src/environment.rs | 23 +++ src/flag.rs | 29 ++++ src/lib.rs | 371 +-------------------------------------------- src/man.rs | 323 +++++++++++++++++++++++++++++++++++++++ src/option.rs | 43 +++++- src/prelude.rs | 16 ++ 7 files changed, 454 insertions(+), 367 deletions(-) create mode 100644 src/man.rs create mode 100644 src/prelude.rs diff --git a/src/author.rs b/src/author.rs index 84f3a3e..69aa0a2 100644 --- a/src/author.rs +++ b/src/author.rs @@ -4,3 +4,19 @@ pub struct Author { pub(crate) name: String, pub(crate) email: Option, } + +impl Author { + /// Create a new instance. + pub fn new(name: &str) -> Self { + Self { + name: name.into(), + email: None, + } + } + + /// Set the email field. + pub fn email(mut self, email: &str) -> Self { + self.email = Some(email.into()); + self + } +} diff --git a/src/environment.rs b/src/environment.rs index b2d1394..2512020 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -5,3 +5,26 @@ pub struct Env { pub(crate) default: Option, pub(crate) description: Option, } + +impl Env { + /// Create a new instance. + pub fn new(name: &str) -> Self { + Self { + name: name.into(), + default: None, + description: None, + } + } + + /// Set the default value. + pub fn default_value(mut self, default: &str) -> Self { + self.default = Some(default.into()); + self + } + + /// Set the description. + pub fn description(mut self, description: &str) -> Self { + self.description = Some(description.into()); + self + } +} diff --git a/src/flag.rs b/src/flag.rs index 8314976..d0e5d45 100644 --- a/src/flag.rs +++ b/src/flag.rs @@ -5,3 +5,32 @@ pub struct Flag { pub(crate) long: Option, pub(crate) description: Option, } + +impl Flag { + /// Create a new instance. + pub fn new() -> Self { + Self { + short: None, + long: None, + description: None, + } + } + + /// Set the short value. + pub fn short(mut self, short: &str) -> Self { + self.short = Some(short.into()); + self + } + + /// Set the long value. + pub fn long(mut self, long: &str) -> Self { + self.long = Some(long.into()); + self + } + + /// Set the description value. + pub fn description(mut self, description: &str) -> Self { + self.description = Some(description.into()); + self + } +} diff --git a/src/lib.rs b/src/lib.rs index 7d7abd5..81633cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,370 +8,13 @@ extern crate roff; mod author; mod environment; mod flag; +mod man; mod option; -use self::author::Author; -use self::environment::Env; -use self::flag::Flag; -use self::option::Opt; -use roff::{bold, italic, list, Roff, Troffable}; -use std::convert::AsRef; +pub mod prelude; -/// Man page struct. -#[derive(Debug, Clone)] -pub struct Man { - name: String, - description: Option, - authors: Vec, - flags: Vec, - options: Vec, - environment: Vec, - arguments: Vec, -} - -impl Man { - /// Create a new instance. - pub fn new(name: &str) -> Self { - Self { - name: name.into(), - description: None, - authors: vec![], - flags: vec![], - options: vec![], - arguments: vec![], - environment: vec![], - } - } - - /// Add a description. - pub fn description(mut self, desc: &str) -> Self { - let desc = desc.into(); - self.description = Some(desc); - self - } - - /// Add an author. - pub fn author( - mut self, - name: impl AsRef, - email: Option, - ) -> Self { - self.authors.push(Author { - name: name.as_ref().to_owned(), - email, - }); - self - } - - /// Add an environment variable. - pub fn environment( - mut self, - name: String, - default: Option, - description: Option, - ) -> Self { - self.environment.push(Env { - name, - default, - description, - }); - self - } - - /// Add an flag. - pub fn flag( - mut self, - short: Option, - long: Option, - description: Option, - ) -> Self { - self.flags.push(Flag { - short, - long, - description, - }); - self - } - - /// Add an option. - pub fn option( - mut self, - short: Option, - long: Option, - description: Option, - argument: String, - default: Option, - ) -> Self { - self.options.push(Opt { - short, - long, - description, - argument, - default, - }); - self - } - - /// Add a positional argument. The items are displayed in the order they're - /// pushed. - // TODO: make this accept argument vecs / optional args too. `arg...`, `arg?` - pub fn argument(mut self, arg: String) -> Self { - self.arguments.push(arg); - self - } - - pub fn render(self) -> String { - let man_num = 1; - let mut page = Roff::new(&self.name, man_num); - page = description(page, &self.name, &self.description); - page = synopsis( - page, - &self.name, - &self.flags, - &self.options, - &self.arguments, - ); - page = flags(page, &self.flags); - page = options(page, &self.options); - page = environment(page, &self.environment); - page = exit_status(page); - page = authors(page, &self.authors); - page.render() - } -} - -/// Create a `NAME` section. -/// -/// ## Formatting -/// ```txt -/// NAME -/// mycmd - brief description of the application -/// ``` -fn description(page: Roff, name: &str, desc: &Option) -> Roff { - let desc = match desc { - Some(ref desc) => format!("{} - {}", name, desc), - None => name.to_owned(), - }; - - page.section("NAME", &[desc]) -} - -/// Create a `SYNOPSIS` section. -fn synopsis( - page: Roff, - name: &str, - flags: &[Flag], - options: &[Opt], - args: &[String], -) -> Roff { - let flags = match flags.len() { - 0 => "".into(), - _ => " [FLAGS]".into(), - }; - let options = match options.len() { - 0 => "".into(), - _ => " [OPTIONS]".into(), - }; - - let mut msg = vec![]; - msg.push(bold(name)); - msg.push(flags); - msg.push(options); - - for arg in args { - msg.push(format!(" {}", arg)); - } - - page.section("SYNOPSIS", &msg) -} - -/// Create a `AUTHOR` or `AUTHORS` section. -/// -/// ## Formatting -/// ```txt -/// AUTHORS -/// Alice Person -/// Bob Human -/// ``` -fn authors(page: Roff, authors: &[Author]) -> Roff { - let title = match authors.len() { - 0 => return page, - 1 => "AUTHOR", - _ => "AUTHORS", - }; - - let last = authors.len() - 1; - let mut auth_values = vec![]; - auth_values.push(init_list()); - for (index, author) in authors.iter().enumerate() { - auth_values.push(author.name.to_owned()); - - if let Some(ref email) = author.email { - auth_values.push(format!(" <{}>", email)) - }; - - if index != last { - auth_values.push(format!("\n")); - } - } - - page.section(title, &auth_values) -} - -/// Create a `FLAGS` section. -/// -/// ## Formatting -/// ```txt -/// FLAGS -/// ``` -fn flags(page: Roff, flags: &[Flag]) -> Roff { - if flags.is_empty() { - return page; - } - - let last = flags.len() - 1; - let mut arr: Vec = vec![]; - for (index, flag) in flags.iter().enumerate() { - let mut args: Vec = vec![]; - if let Some(ref short) = flag.short { - args.push(bold(&short)); - } - if let Some(ref long) = flag.long { - if !args.is_empty() { - args.push(", ".to_string()); - } - args.push(bold(&long)); - } - let desc = match flag.description { - Some(ref desc) => desc.to_string(), - None => "".to_string(), - }; - arr.push(list(&args, &[desc])); - - if index != last { - arr.push(format!("\n\n")); - } - } - page.section("FLAGS", &arr) -} - -/// Create a `OPTIONS` section. -/// -/// ## Formatting -/// ```txt -/// OPTIONS -/// ``` -fn options(page: Roff, options: &[Opt]) -> Roff { - if options.is_empty() { - return page; - } - - let last = options.len() - 1; - let mut arr: Vec = vec![]; - for (index, opt) in options.iter().enumerate() { - let mut args: Vec = vec![]; - if let Some(ref short) = opt.short { - args.push(bold(&short)); - } - if let Some(ref long) = opt.long { - if !args.is_empty() { - args.push(", ".to_string()); - } - args.push(bold(&long)); - } - args.push("=".into()); - args.push(italic(&opt.argument)); - if let Some(ref default) = opt.default { - if !args.is_empty() { - args.push(" ".to_string()); - } - args.push("[".into()); - args.push("default:".into()); - args.push(" ".into()); - args.push(italic(&default)); - args.push("]".into()); - } - let desc = match opt.description { - Some(ref desc) => desc.to_string(), - None => "".to_string(), - }; - arr.push(list(&args, &[desc])); - - if index != last { - arr.push(format!("\n\n")); - } - } - page.section("OPTIONS", &arr) -} - -/// Create a `ENVIRONMENT` section. -/// -/// ## Formatting -/// ```txt -/// ENVIRONMENT -/// ``` -fn environment(page: Roff, environment: &[Env]) -> Roff { - if environment.is_empty() { - return page; - } - - let last = environment.len() - 1; - let mut arr: Vec = vec![]; - for (index, env) in environment.iter().enumerate() { - let mut args: Vec = vec![]; - args.push(bold(&env.name)); - if let Some(ref default) = env.default { - if !args.is_empty() { - args.push(" ".to_string()); - } - args.push("[".into()); - args.push("default:".into()); - args.push(" ".into()); - args.push(italic(&default)); - args.push("]".into()); - } - let desc = match env.description { - Some(ref desc) => desc.to_string(), - None => "".to_string(), - }; - arr.push(list(&args, &[desc])); - - if index != last { - arr.push(format!("\n\n")); - } - } - page.section("ENVIRONMENT", &arr) -} - -/// Create a `EXIT STATUS` section. -/// -/// ## Implementation Note -/// This currently only returns the status code `0`, and takes no arguments. We -/// should let it take arguments. -/// -/// ## Formatting -/// ```txt -/// EXIT STATUS -/// 0 Successful program execution -/// -/// 1 Usage, syntax or configuration file error -/// -/// 2 Optional error -/// ``` -fn exit_status(page: Roff) -> Roff { - page.section( - "EXIT STATUS", - &[list(&[bold("0")], &["Successful program execution."])], - ) -} - -// NOTE(yw): This code was taken from the npm-install(1) command. The location -// on your system may vary. In all honesty I just copy-pasted this. We should -// probably port this to troff-rs at some point. -// -// ```sh -// $ less /usr/share/man/man1/npm-install.1 -// ``` -fn init_list() -> String { - format!(".P\n.RS 2\n.nf\n") -} +pub use author::Author; +pub use environment::Env; +pub use flag::Flag; +pub use man::Man; +pub use option::Opt; diff --git a/src/man.rs b/src/man.rs new file mode 100644 index 0000000..807242a --- /dev/null +++ b/src/man.rs @@ -0,0 +1,323 @@ +use super::*; +use roff::{bold, italic, list, Roff, Troffable}; + +/// Man page struct. +#[derive(Debug, Clone)] +pub struct Man { + name: String, + description: Option, + authors: Vec, + flags: Vec, + options: Vec, + environment: Vec, + arguments: Vec, +} + +impl Man { + /// Create a new instance. + pub fn new(name: &str) -> Self { + Self { + name: name.into(), + description: None, + authors: vec![], + flags: vec![], + options: vec![], + arguments: vec![], + environment: vec![], + } + } + + /// Add a description. + pub fn description(mut self, desc: String) -> Self { + self.description = Some(desc); + self + } + + /// Add an author. + pub fn author(mut self, author: Author) -> Self { + self.authors.push(author); + self + } + + /// Add an environment variable. + pub fn environment(mut self, env: Env) -> Self { + self.environment.push(env); + self + } + + /// Add an flag. + pub fn flag(mut self, flag: Flag) -> Self { + self.flags.push(flag); + self + } + + /// Add an option. + pub fn option(mut self, opt: Opt) -> Self { + self.options.push(opt); + self + } + + /// Add a positional argument. The items are displayed in the order they're + /// pushed. + // TODO: make this accept argument vecs / optional args too. `arg...`, `arg?` + pub fn argument(mut self, arg: String) -> Self { + self.arguments.push(arg); + self + } + + /// Render to a string. + pub fn render(self) -> String { + let man_num = 1; + let mut page = Roff::new(&self.name, man_num); + page = description(page, &self.name, &self.description); + page = synopsis( + page, + &self.name, + &self.flags, + &self.options, + &self.arguments, + ); + page = flags(page, &self.flags); + page = options(page, &self.options); + page = environment(page, &self.environment); + page = exit_status(page); + page = authors(page, &self.authors); + page.render() + } +} + +/// Create a `NAME` section. +/// +/// ## Formatting +/// ```txt +/// NAME +/// mycmd - brief description of the application +/// ``` +fn description(page: Roff, name: &str, desc: &Option) -> Roff { + let desc = match desc { + Some(ref desc) => format!("{} - {}", name, desc), + None => name.to_owned(), + }; + + page.section("NAME", &[desc]) +} + +/// Create a `SYNOPSIS` section. +fn synopsis( + page: Roff, + name: &str, + flags: &[Flag], + options: &[Opt], + args: &[String], +) -> Roff { + let flags = match flags.len() { + 0 => "".into(), + _ => " [FLAGS]".into(), + }; + let options = match options.len() { + 0 => "".into(), + _ => " [OPTIONS]".into(), + }; + + let mut msg = vec![]; + msg.push(bold(name)); + msg.push(flags); + msg.push(options); + + for arg in args { + msg.push(format!(" {}", arg)); + } + + page.section("SYNOPSIS", &msg) +} + +/// Create a `AUTHOR` or `AUTHORS` section. +/// +/// ## Formatting +/// ```txt +/// AUTHORS +/// Alice Person +/// Bob Human +/// ``` +fn authors(page: Roff, authors: &[Author]) -> Roff { + let title = match authors.len() { + 0 => return page, + 1 => "AUTHOR", + _ => "AUTHORS", + }; + + let last = authors.len() - 1; + let mut auth_values = vec![]; + auth_values.push(init_list()); + for (index, author) in authors.iter().enumerate() { + auth_values.push(author.name.to_owned()); + + if let Some(ref email) = author.email { + auth_values.push(format!(" <{}>", email)) + }; + + if index != last { + auth_values.push(format!("\n")); + } + } + + page.section(title, &auth_values) +} + +/// Create a `FLAGS` section. +/// +/// ## Formatting +/// ```txt +/// FLAGS +/// ``` +fn flags(page: Roff, flags: &[Flag]) -> Roff { + if flags.is_empty() { + return page; + } + + let last = flags.len() - 1; + let mut arr: Vec = vec![]; + for (index, flag) in flags.iter().enumerate() { + let mut args: Vec = vec![]; + if let Some(ref short) = flag.short { + args.push(bold(&short)); + } + if let Some(ref long) = flag.long { + if !args.is_empty() { + args.push(", ".to_string()); + } + args.push(bold(&long)); + } + let desc = match flag.description { + Some(ref desc) => desc.to_string(), + None => "".to_string(), + }; + arr.push(list(&args, &[desc])); + + if index != last { + arr.push(format!("\n\n")); + } + } + page.section("FLAGS", &arr) +} + +/// Create a `OPTIONS` section. +/// +/// ## Formatting +/// ```txt +/// OPTIONS +/// ``` +fn options(page: Roff, options: &[Opt]) -> Roff { + if options.is_empty() { + return page; + } + + let last = options.len() - 1; + let mut arr: Vec = vec![]; + for (index, opt) in options.iter().enumerate() { + let mut args: Vec = vec![]; + if let Some(ref short) = opt.short { + args.push(bold(&short)); + } + if let Some(ref long) = opt.long { + if !args.is_empty() { + args.push(", ".to_string()); + } + args.push(bold(&long)); + } + args.push("=".into()); + args.push(italic(&opt.name)); + if let Some(ref default) = opt.default { + if !args.is_empty() { + args.push(" ".to_string()); + } + args.push("[".into()); + args.push("default:".into()); + args.push(" ".into()); + args.push(italic(&default)); + args.push("]".into()); + } + let desc = match opt.description { + Some(ref desc) => desc.to_string(), + None => "".to_string(), + }; + arr.push(list(&args, &[desc])); + + if index != last { + arr.push(format!("\n\n")); + } + } + page.section("OPTIONS", &arr) +} + +/// Create a `ENVIRONMENT` section. +/// +/// ## Formatting +/// ```txt +/// ENVIRONMENT +/// ``` +fn environment(page: Roff, environment: &[Env]) -> Roff { + if environment.is_empty() { + return page; + } + + let last = environment.len() - 1; + let mut arr: Vec = vec![]; + for (index, env) in environment.iter().enumerate() { + let mut args: Vec = vec![]; + args.push(bold(&env.name)); + if let Some(ref default) = env.default { + if !args.is_empty() { + args.push(" ".to_string()); + } + args.push("[".into()); + args.push("default:".into()); + args.push(" ".into()); + args.push(italic(&default)); + args.push("]".into()); + } + let desc = match env.description { + Some(ref desc) => desc.to_string(), + None => "".to_string(), + }; + arr.push(list(&args, &[desc])); + + if index != last { + arr.push(format!("\n\n")); + } + } + page.section("ENVIRONMENT", &arr) +} + +/// Create a `EXIT STATUS` section. +/// +/// ## Implementation Note +/// This currently only returns the status code `0`, and takes no arguments. We +/// should let it take arguments. +/// +/// ## Formatting +/// ```txt +/// EXIT STATUS +/// 0 Successful program execution +/// +/// 1 Usage, syntax or configuration file error +/// +/// 2 Optional error +/// ``` +fn exit_status(page: Roff) -> Roff { + page.section( + "EXIT STATUS", + &[list(&[bold("0")], &["Successful program execution."])], + ) +} + +// NOTE(yw): This code was taken from the npm-install(1) command. The location +// on your system may vary. In all honesty I just copy-pasted this. We should +// probably port this to troff-rs at some point. +// +// ```sh +// $ less /usr/share/man/man1/npm-install.1 +// ``` +fn init_list() -> String { + format!(".P\n.RS 2\n.nf\n") +} diff --git a/src/option.rs b/src/option.rs index cb477b1..524c7a2 100644 --- a/src/option.rs +++ b/src/option.rs @@ -1,9 +1,46 @@ /// Option #[derive(Debug, Clone)] pub struct Opt { + pub(crate) name: String, + pub(crate) default: Option, + pub(crate) description: Option, pub(crate) short: Option, pub(crate) long: Option, - pub(crate) description: Option, - pub(crate) argument: String, - pub(crate) default: Option, +} + +impl Opt { + /// Create a new instance. + pub fn new(name: &str) -> Self { + Self { + name: name.into(), + default: None, + description: None, + short: None, + long: None, + } + } + + /// Set the default value. + pub fn default_value(mut self, default: &str) -> Self { + self.default = Some(default.into()); + self + } + + /// Set the description. + pub fn description(mut self, description: &str) -> Self { + self.description = Some(description.into()); + self + } + + /// Set the short value. + pub fn short(mut self, short: &str) -> Self { + self.short = Some(short.into()); + self + } + + /// Set the long value. + pub fn long(mut self, long: &str) -> Self { + self.long = Some(long.into()); + self + } } diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 0000000..6680291 --- /dev/null +++ b/src/prelude.rs @@ -0,0 +1,16 @@ +//! Convenience wrapper to import all the essential structs. +//! +//! ```rust +//! extern crate man; +//! +//! use man::prelude::*; +//! +//! fn main () { +//! let msg = Man::new("my-app").render(); +//! } +//! ``` +pub use author::Author; +pub use environment::Env; +pub use flag::Flag; +pub use man::Man; +pub use option::Opt; From fa2e65845183a5f660a75de1854169515070ac6e Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Fri, 3 Aug 2018 14:25:07 +0200 Subject: [PATCH 3/8] port example to builder pattern --- examples/main.rs | 53 ++++++++++++++++++++---------------------------- src/man.rs | 4 ++-- 2 files changed, 24 insertions(+), 33 deletions(-) diff --git a/examples/main.rs b/examples/main.rs index e3306eb..fc8db70 100644 --- a/examples/main.rs +++ b/examples/main.rs @@ -1,47 +1,38 @@ extern crate man; -use man::Man; +use man::prelude::*; fn main() { let msg = Man::new("auth-service") - .description("authorize & authenticate members") - .argument("path".into()) - .environment( - "PORT".into(), - None, - Some("The network port to listen to.".into()), - ) + .description("authorize & authenticate members".into()) + .argument("path") + .environment(Env::new("PORT").description("The network port to listen to")) .flag( - Some("-h".into()), - Some("--help".into()), - Some("Prints help information.".into()), + Flag::new() + .short("-h") + .long("--help") + .description("Prints help information"), ) .flag( - Some("-V".into()), - Some("--version".into()), - Some("Prints version information.".into()), + Flag::new() + .short("-V") + .long("--version") + .description("Prints version information"), ) .flag( - Some("-v".into()), - Some("--verbosity".into()), - Some("Pass multiple times to print more information.".into()), - ) - .option( - Some("-a".into()), - Some("--address".into()), - Some("The network address to listen to.".into()), - "address".into(), - Some("127.0.0.1".into()), + Flag::new() + .short("-v") + .long("--verbosity") + .description("Pass multiple times to print more information"), ) .option( - Some("-p".into()), - Some("--port".into()), - Some("The network port to listen to.".into()), - "port".into(), - None, + Opt::new("port") + .short("-p") + .long("--port") + .description("The network port to listen to"), ) - .author("Alice Person", Some("alice@person.com".into())) - .author("Bob Human", Some("bob@human.com".into())) + .author(Author::new("Alice Person").email("alice@person.com")) + .author(Author::new("Bob Human").email("bob@human.com")) .render(); // .option(Some("-o"), Some("--output"), "output", None, "Output file"); diff --git a/src/man.rs b/src/man.rs index 807242a..cecc004 100644 --- a/src/man.rs +++ b/src/man.rs @@ -60,8 +60,8 @@ impl Man { /// Add a positional argument. The items are displayed in the order they're /// pushed. // TODO: make this accept argument vecs / optional args too. `arg...`, `arg?` - pub fn argument(mut self, arg: String) -> Self { - self.arguments.push(arg); + pub fn argument(mut self, arg: &str) -> Self { + self.arguments.push(arg.into()); self } From ef1d76b2c7fb12139555d91d2be0f83842494c76 Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Fri, 3 Aug 2018 14:27:25 +0200 Subject: [PATCH 4/8] update readme --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5836b6b..b990ba9 100644 --- a/README.md +++ b/README.md @@ -12,18 +12,18 @@ Generate structured man pages using ```rust extern crate man; -use man::Man; +use man::prelude::*; fn main() { let page = Man::new("basic") .description("A basic example") - .author("Alice", Some("alice@email.com")) - .author("Bob", Some("bob@email.com")) - .flag(Some("-d"), Some("--debug"), Some("Activate debug mode")) - .flag(Some("-v"), Some("--verbose"), Some("Verbose mode")); - .option(Some("-o"), Some("--output"), "output", None, "Output file"); + .author(Author::new("Alice Person").email("alice@person.com")) + .author(Author::new("Bob Human").email("bob@human.com")) + .flag(Flag::new().short("-d").long("--deubg").description("Enable debug mode")) + .flag(Flag::new().short("-v").long("--verbose").description("Enable verbose mode")) + .option(Opt::new("output").short("-o").long("--output").description("Output file")); - let _string = page.to_string(); + let _string = page.render(); } ``` Preview by running: From 03ecbeb8221d1079dece1da701ebece3dc316c6c Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Fri, 3 Aug 2018 14:33:25 +0200 Subject: [PATCH 5/8] s/description/help/g --- examples/main.rs | 12 ++++++------ src/environment.rs | 10 +++++----- src/flag.rs | 10 +++++----- src/man.rs | 22 +++++++++++----------- src/mod.rs | 34 +++++++++++++++++----------------- src/option.rs | 10 +++++----- 6 files changed, 49 insertions(+), 49 deletions(-) diff --git a/examples/main.rs b/examples/main.rs index fc8db70..82efb00 100644 --- a/examples/main.rs +++ b/examples/main.rs @@ -4,32 +4,32 @@ use man::prelude::*; fn main() { let msg = Man::new("auth-service") - .description("authorize & authenticate members".into()) + .help("authorize & authenticate members".into()) .argument("path") - .environment(Env::new("PORT").description("The network port to listen to")) + .environment(Env::new("PORT").help("The network port to listen to")) .flag( Flag::new() .short("-h") .long("--help") - .description("Prints help information"), + .help("Prints help information"), ) .flag( Flag::new() .short("-V") .long("--version") - .description("Prints version information"), + .help("Prints version information"), ) .flag( Flag::new() .short("-v") .long("--verbosity") - .description("Pass multiple times to print more information"), + .help("Pass multiple times to print more information"), ) .option( Opt::new("port") .short("-p") .long("--port") - .description("The network port to listen to"), + .help("The network port to listen to"), ) .author(Author::new("Alice Person").email("alice@person.com")) .author(Author::new("Bob Human").email("bob@human.com")) diff --git a/src/environment.rs b/src/environment.rs index 2512020..0e51bce 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -3,7 +3,7 @@ pub struct Env { pub(crate) name: String, pub(crate) default: Option, - pub(crate) description: Option, + pub(crate) help: Option, } impl Env { @@ -12,7 +12,7 @@ impl Env { Self { name: name.into(), default: None, - description: None, + help: None, } } @@ -22,9 +22,9 @@ impl Env { self } - /// Set the description. - pub fn description(mut self, description: &str) -> Self { - self.description = Some(description.into()); + /// Set the help. + pub fn help(mut self, help: &str) -> Self { + self.help = Some(help.into()); self } } diff --git a/src/flag.rs b/src/flag.rs index d0e5d45..1463706 100644 --- a/src/flag.rs +++ b/src/flag.rs @@ -3,7 +3,7 @@ pub struct Flag { pub(crate) short: Option, pub(crate) long: Option, - pub(crate) description: Option, + pub(crate) help: Option, } impl Flag { @@ -12,7 +12,7 @@ impl Flag { Self { short: None, long: None, - description: None, + help: None, } } @@ -28,9 +28,9 @@ impl Flag { self } - /// Set the description value. - pub fn description(mut self, description: &str) -> Self { - self.description = Some(description.into()); + /// Set the help value. + pub fn help(mut self, help: &str) -> Self { + self.help = Some(help.into()); self } } diff --git a/src/man.rs b/src/man.rs index cecc004..6244357 100644 --- a/src/man.rs +++ b/src/man.rs @@ -5,7 +5,7 @@ use roff::{bold, italic, list, Roff, Troffable}; #[derive(Debug, Clone)] pub struct Man { name: String, - description: Option, + help: Option, authors: Vec, flags: Vec, options: Vec, @@ -18,7 +18,7 @@ impl Man { pub fn new(name: &str) -> Self { Self { name: name.into(), - description: None, + help: None, authors: vec![], flags: vec![], options: vec![], @@ -27,9 +27,9 @@ impl Man { } } - /// Add a description. - pub fn description(mut self, desc: String) -> Self { - self.description = Some(desc); + /// Add a help. + pub fn help(mut self, desc: String) -> Self { + self.help = Some(desc); self } @@ -69,7 +69,7 @@ impl Man { pub fn render(self) -> String { let man_num = 1; let mut page = Roff::new(&self.name, man_num); - page = description(page, &self.name, &self.description); + page = help(page, &self.name, &self.help); page = synopsis( page, &self.name, @@ -91,9 +91,9 @@ impl Man { /// ## Formatting /// ```txt /// NAME -/// mycmd - brief description of the application +/// mycmd - brief help of the application /// ``` -fn description(page: Roff, name: &str, desc: &Option) -> Roff { +fn help(page: Roff, name: &str, desc: &Option) -> Roff { let desc = match desc { Some(ref desc) => format!("{} - {}", name, desc), None => name.to_owned(), @@ -188,7 +188,7 @@ fn flags(page: Roff, flags: &[Flag]) -> Roff { } args.push(bold(&long)); } - let desc = match flag.description { + let desc = match flag.help { Some(ref desc) => desc.to_string(), None => "".to_string(), }; @@ -237,7 +237,7 @@ fn options(page: Roff, options: &[Opt]) -> Roff { args.push(italic(&default)); args.push("]".into()); } - let desc = match opt.description { + let desc = match opt.help { Some(ref desc) => desc.to_string(), None => "".to_string(), }; @@ -276,7 +276,7 @@ fn environment(page: Roff, environment: &[Env]) -> Roff { args.push(italic(&default)); args.push("]".into()); } - let desc = match env.description { + let desc = match env.help { Some(ref desc) => desc.to_string(), None => "".to_string(), }; diff --git a/src/mod.rs b/src/mod.rs index 0739720..cb7a1a5 100644 --- a/src/mod.rs +++ b/src/mod.rs @@ -14,7 +14,7 @@ use std::convert::AsRef; #[derive(Debug, Clone)] pub struct Man { name: String, - description: Option, + help: Option, authors: Vec, flags: Vec, options: Vec, @@ -27,7 +27,7 @@ impl Man { pub fn new(name: &str) -> Self { Self { name: name.into(), - description: None, + help: None, authors: vec![], flags: vec![], options: vec![], @@ -36,10 +36,10 @@ impl Man { } } - /// Add a description. - pub fn description(mut self, desc: &str) -> Self { + /// Add a help. + pub fn help(mut self, desc: &str) -> Self { let desc = desc.into(); - self.description = Some(desc); + self.help = Some(desc); self } @@ -61,12 +61,12 @@ impl Man { mut self, name: String, default: Option, - description: Option, + help: Option, ) -> Self { self.environment.push(Env { name, default, - description, + help, }); self } @@ -76,12 +76,12 @@ impl Man { mut self, short: Option, long: Option, - description: Option, + help: Option, ) -> Self { self.flags.push(Flag { short, long, - description, + help, }); self } @@ -91,14 +91,14 @@ impl Man { mut self, short: Option, long: Option, - description: Option, + help: Option, argument: String, default: Option, ) -> Self { self.options.push(Opt { short, long, - description, + help, argument, default, }); @@ -116,7 +116,7 @@ impl Man { pub fn render(self) -> String { let man_num = 1; let mut page = Roff::new(&self.name, man_num); - page = description(page, &self.name, &self.description); + page = help(page, &self.name, &self.help); page = synopsis( page, &self.name, @@ -138,9 +138,9 @@ impl Man { /// ## Formatting /// ```txt /// NAME -/// mycmd - brief description of the application +/// mycmd - brief help of the application /// ``` -fn description(page: Roff, name: &str, desc: &Option) -> Roff { +fn help(page: Roff, name: &str, desc: &Option) -> Roff { let desc = match desc { Some(ref desc) => format!("{} - {}", name, desc), None => name.to_owned(), @@ -235,7 +235,7 @@ fn flags(page: Roff, flags: &[Flag]) -> Roff { } args.push(bold(&long)); } - let desc = match flag.description { + let desc = match flag.help { Some(ref desc) => desc.to_string(), None => "".to_string(), }; @@ -284,7 +284,7 @@ fn options(page: Roff, options: &[Opt]) -> Roff { args.push(italic(&default)); args.push("]".into()); } - let desc = match opt.description { + let desc = match opt.help { Some(ref desc) => desc.to_string(), None => "".to_string(), }; @@ -323,7 +323,7 @@ fn environment(page: Roff, environment: &[Env]) -> Roff { args.push(italic(&default)); args.push("]".into()); } - let desc = match env.description { + let desc = match env.help { Some(ref desc) => desc.to_string(), None => "".to_string(), }; diff --git a/src/option.rs b/src/option.rs index 524c7a2..127227d 100644 --- a/src/option.rs +++ b/src/option.rs @@ -3,7 +3,7 @@ pub struct Opt { pub(crate) name: String, pub(crate) default: Option, - pub(crate) description: Option, + pub(crate) help: Option, pub(crate) short: Option, pub(crate) long: Option, } @@ -14,7 +14,7 @@ impl Opt { Self { name: name.into(), default: None, - description: None, + help: None, short: None, long: None, } @@ -26,9 +26,9 @@ impl Opt { self } - /// Set the description. - pub fn description(mut self, description: &str) -> Self { - self.description = Some(description.into()); + /// Set the help. + pub fn help(mut self, help: &str) -> Self { + self.help = Some(help.into()); self } From c49bd80dd0da1cfbd780710e5a61121670a16d61 Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Fri, 3 Aug 2018 14:48:08 +0200 Subject: [PATCH 6/8] arg struct --- examples/main.rs | 14 +++++++------- src/arg.rs | 10 ++++++++++ src/lib.rs | 2 ++ src/man.rs | 30 +++++++++++++++--------------- src/prelude.rs | 1 + 5 files changed, 35 insertions(+), 22 deletions(-) create mode 100644 src/arg.rs diff --git a/examples/main.rs b/examples/main.rs index 82efb00..03a2a65 100644 --- a/examples/main.rs +++ b/examples/main.rs @@ -4,32 +4,32 @@ use man::prelude::*; fn main() { let msg = Man::new("auth-service") - .help("authorize & authenticate members".into()) - .argument("path") - .environment(Env::new("PORT").help("The network port to listen to")) + .about("authorize & authenticate members".into()) + .arg(Arg::new("path")) + .env(Env::new("PORT").help("The network port to listen to")) .flag( Flag::new() .short("-h") .long("--help") - .help("Prints help information"), + .help("Prints help information."), ) .flag( Flag::new() .short("-V") .long("--version") - .help("Prints version information"), + .help("Prints version information."), ) .flag( Flag::new() .short("-v") .long("--verbosity") - .help("Pass multiple times to print more information"), + .help("Pass multiple times to print more information."), ) .option( Opt::new("port") .short("-p") .long("--port") - .help("The network port to listen to"), + .help("The network port to listen to."), ) .author(Author::new("Alice Person").email("alice@person.com")) .author(Author::new("Bob Human").email("bob@human.com")) diff --git a/src/arg.rs b/src/arg.rs new file mode 100644 index 0000000..a12370f --- /dev/null +++ b/src/arg.rs @@ -0,0 +1,10 @@ +#[derive(Debug, Clone)] +pub struct Arg { + pub(crate) name: String, +} + +impl Arg { + pub fn new(name: &str) -> Self { + Self { name: name.into() } + } +} diff --git a/src/lib.rs b/src/lib.rs index 81633cb..a2570d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ extern crate roff; +mod arg; mod author; mod environment; mod flag; @@ -13,6 +14,7 @@ mod option; pub mod prelude; +pub use arg::Arg; pub use author::Author; pub use environment::Env; pub use flag::Flag; diff --git a/src/man.rs b/src/man.rs index 6244357..d956fb9 100644 --- a/src/man.rs +++ b/src/man.rs @@ -5,12 +5,12 @@ use roff::{bold, italic, list, Roff, Troffable}; #[derive(Debug, Clone)] pub struct Man { name: String, - help: Option, + about: Option, authors: Vec, flags: Vec, options: Vec, environment: Vec, - arguments: Vec, + arguments: Vec, } impl Man { @@ -18,7 +18,7 @@ impl Man { pub fn new(name: &str) -> Self { Self { name: name.into(), - help: None, + about: None, authors: vec![], flags: vec![], options: vec![], @@ -27,9 +27,9 @@ impl Man { } } - /// Add a help. - pub fn help(mut self, desc: String) -> Self { - self.help = Some(desc); + /// Add a description. + pub fn about(mut self, about: String) -> Self { + self.about = Some(about); self } @@ -40,7 +40,7 @@ impl Man { } /// Add an environment variable. - pub fn environment(mut self, env: Env) -> Self { + pub fn env(mut self, env: Env) -> Self { self.environment.push(env); self } @@ -60,8 +60,8 @@ impl Man { /// Add a positional argument. The items are displayed in the order they're /// pushed. // TODO: make this accept argument vecs / optional args too. `arg...`, `arg?` - pub fn argument(mut self, arg: &str) -> Self { - self.arguments.push(arg.into()); + pub fn arg(mut self, arg: Arg) -> Self { + self.arguments.push(arg); self } @@ -69,7 +69,7 @@ impl Man { pub fn render(self) -> String { let man_num = 1; let mut page = Roff::new(&self.name, man_num); - page = help(page, &self.name, &self.help); + page = about(page, &self.name, &self.about); page = synopsis( page, &self.name, @@ -79,7 +79,7 @@ impl Man { ); page = flags(page, &self.flags); page = options(page, &self.options); - page = environment(page, &self.environment); + page = env(page, &self.environment); page = exit_status(page); page = authors(page, &self.authors); page.render() @@ -93,7 +93,7 @@ impl Man { /// NAME /// mycmd - brief help of the application /// ``` -fn help(page: Roff, name: &str, desc: &Option) -> Roff { +fn about(page: Roff, name: &str, desc: &Option) -> Roff { let desc = match desc { Some(ref desc) => format!("{} - {}", name, desc), None => name.to_owned(), @@ -108,7 +108,7 @@ fn synopsis( name: &str, flags: &[Flag], options: &[Opt], - args: &[String], + args: &[Arg], ) -> Roff { let flags = match flags.len() { 0 => "".into(), @@ -125,7 +125,7 @@ fn synopsis( msg.push(options); for arg in args { - msg.push(format!(" {}", arg)); + msg.push(format!(" {}", arg.name)); } page.section("SYNOPSIS", &msg) @@ -256,7 +256,7 @@ fn options(page: Roff, options: &[Opt]) -> Roff { /// ```txt /// ENVIRONMENT /// ``` -fn environment(page: Roff, environment: &[Env]) -> Roff { +fn env(page: Roff, environment: &[Env]) -> Roff { if environment.is_empty() { return page; } diff --git a/src/prelude.rs b/src/prelude.rs index 6680291..3a04a29 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -9,6 +9,7 @@ //! let msg = Man::new("my-app").render(); //! } //! ``` +pub use arg::Arg; pub use author::Author; pub use environment::Env; pub use flag::Flag; From a8ba1a73dc7868802d6e834144795393d8bd367a Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Fri, 3 Aug 2018 14:56:53 +0200 Subject: [PATCH 7/8] s/Man/Manual/ --- README.md | 4 ++-- examples/main.rs | 2 +- src/lib.rs | 2 +- src/man.rs | 4 ++-- src/prelude.rs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b990ba9..a77cc23 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ extern crate man; use man::prelude::*; fn main() { - let page = Man::new("basic") - .description("A basic example") + let page = Manual::new("basic") + .about("A basic example") .author(Author::new("Alice Person").email("alice@person.com")) .author(Author::new("Bob Human").email("bob@human.com")) .flag(Flag::new().short("-d").long("--deubg").description("Enable debug mode")) diff --git a/examples/main.rs b/examples/main.rs index 03a2a65..98cf5d6 100644 --- a/examples/main.rs +++ b/examples/main.rs @@ -3,7 +3,7 @@ extern crate man; use man::prelude::*; fn main() { - let msg = Man::new("auth-service") + let msg = Manual::new("auth-service") .about("authorize & authenticate members".into()) .arg(Arg::new("path")) .env(Env::new("PORT").help("The network port to listen to")) diff --git a/src/lib.rs b/src/lib.rs index a2570d7..d3c3fd6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,5 +18,5 @@ pub use arg::Arg; pub use author::Author; pub use environment::Env; pub use flag::Flag; -pub use man::Man; +pub use man::Manual; pub use option::Opt; diff --git a/src/man.rs b/src/man.rs index d956fb9..7b06424 100644 --- a/src/man.rs +++ b/src/man.rs @@ -3,7 +3,7 @@ use roff::{bold, italic, list, Roff, Troffable}; /// Man page struct. #[derive(Debug, Clone)] -pub struct Man { +pub struct Manual { name: String, about: Option, authors: Vec, @@ -13,7 +13,7 @@ pub struct Man { arguments: Vec, } -impl Man { +impl Manual { /// Create a new instance. pub fn new(name: &str) -> Self { Self { diff --git a/src/prelude.rs b/src/prelude.rs index 3a04a29..89a3b20 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -13,5 +13,5 @@ pub use arg::Arg; pub use author::Author; pub use environment::Env; pub use flag::Flag; -pub use man::Man; +pub use man::Manual; pub use option::Opt; From 1b67dbcac040af6cdbee70c05e542772ac892b3b Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Mon, 6 Aug 2018 10:23:22 +0200 Subject: [PATCH 8/8] more exit codes --- src/man.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/man.rs b/src/man.rs index 7b06424..5357dc1 100644 --- a/src/man.rs +++ b/src/man.rs @@ -307,7 +307,11 @@ fn env(page: Roff, environment: &[Env]) -> Roff { fn exit_status(page: Roff) -> Roff { page.section( "EXIT STATUS", - &[list(&[bold("0")], &["Successful program execution."])], + &[ + list(&[bold("0")], &["Successful program execution.\n\n"]), + list(&[bold("1")], &["Unsuccessful program execution.\n\n"]), + list(&[bold("101")], &["The program panicked."]), + ], ) }