commandy 0.2.5

easy parsing of command line arguments
Documentation
use std::env;

use commandy::FlagParser;
use commandy_macros::*;

#[derive(Command,Debug,Default)]
#[command("set")]
#[description("set a specific path")]
struct SetCommand {
}

#[derive(Command,Debug,Default)]
#[command("get")]
#[description("get a specific path")]
struct GetCommand {
    /// base dir relative to which `filehash()` and `filewithhash()` works
    #[flag("-b", "--base", "base")]
    base: String,
    #[flag("-z", "foo")]
    /// some flag
    some_flag: bool,
    #[positional("markdown-file",1,2)]
    path: Vec<String>,
}

#[derive(Command,Debug)]
#[allow(dead_code)]
enum Sub {
    Get(GetCommand),
    Set(SetCommand),
}

// FIXME: default can be eliminated by having a different FlagParser (that returns the value
// instead of modifies self)
#[derive(Debug,Default)]
enum FlagOption {
    #[default]
    Get,
    Set,
}

impl FlagParser for FlagOption {
    fn parse_flag(&mut self, args: &[&str]) -> Result<(), String> {
        if args == ["get"] {
            *self = FlagOption::Get;
            return Ok(());
        }
        if args == ["set"] {
            *self = FlagOption::Set;
            return Ok(());
        }
        Err(String::from("either `get` or `set` expected"))
    }
}

#[derive(Command,Debug,Default)]
#[description("md2h: Converts markdown to HTML")]
#[program("commandytester")] // can have an argument, outputs man-page + shell completion
#[related("foo", 2)]
#[related("ls", 1)]
/// Convert Bit Powder Markdown to HTML. Overview of Bit Powder Markdown:
/// .Bl -bullet
/// .It
/// supports attributes with `{attr}` syntax, multiple attributes delimited with `;`. These attributes can be:
/// .Bl -bullet -compact
/// .It
/// CSS classes with: `.class`
/// .It
/// CSS properties with: `key: value`
/// .It
/// HTML tag attributes with: `key=value`
/// .It
/// HTML ids with: `#id`
/// .El
/// .It
/// spans with `[content]`, divs with `[[content]]`;
/// .It
/// marks with `==content==`, and abbrs with `=content=`;
/// .It
/// superscript with `x^^[2]`, and subscript with `CO~~[2]`;
/// .It
/// icons with `:fas-hexagon:`;
/// .It
/// as-is copy to output with `[[[ as-is-content ]]]`;
/// .It
/// reports errors when encountering syntax errors (so does not continue like regular Markdown);
/// .It
/// no tables as of yet.
/// .El
/// 
/// This utility also supports Bit Powder Mustache templates. Variables can be declared at top of an input file with `key: json-value` pairs. The templates supports:
/// .Bl -bullet
/// .It
/// values with `{{value}}`;
/// .It
/// iterators and conditional with `{{#value}}contents{{/}}`.
/// Contents is displayed (if this default form is used) if value is an object, array of objects, or true.
/// An iteration variable can be specified with `{{#name -> variable}}content{{/}}`, which can be used to iterate over (arrays of) any type.
/// Iterators can have optional default values (in `object`) with `{{#name <- object}}content{{/}}` (these object defaults are evaluated once during parsing of the file and can reference global variables);
/// .It
/// template definition with `{{=name}}content{{/}}`.
/// Templates can have optional default values (in `object`) with `{{=name <- object}}content{{/}}`;
/// .It
/// template instantation with `{{>name}}` or `{{name <- value}}`;
/// .It
/// Error handling is different from normal Mustache: missing fields is always considered an error. Errors can be suppressed with the `expr ??? alternative` try syntax (on error in `expr`, `alternative` will be executed). Shorthand for `expr ??? null` is `expr ???` (which can be used in loops with `{{#someField???}}` (which ignores errors if the field is not found).
/// .El
/// 
/// Binary Object expressions can be used instead of values as above (think of expressions, functional calls, selectors, etc). This utility supports also Lua in the templates. Prefix anywhere a value is accepted with `lua:` to run Lua code (with current value in `this`).
/// 
/// There are a couple of special functions added to the Binary Object expressions:
/// .Bl -tag -width Ds
/// .It Ft string Fn compile "string template, [any context]"
/// Compiles a string to a dynamic Mustache template that can be evaluated, and evaluates the defaults using the optional context
/// .Ar context
/// (otherwise empty context is used).
/// .It Ft string Fn eval "string compiled_template, any context"
/// Evaluates a dynamic Mustache template using the context
/// .Ar context .
/// .It Ft void Fn set "string key, any value"
/// .It Ft any Fn get "string key"
/// Sets/retrieves a global value (useful for figure numbers etc).
/// .It Ft string Fn filehash "string path"
/// .It Ft string Fn filewithhash "string path"
/// Calculates the xxhash32 for the contents of the file specified by path (relative to the --base flag). Useful to support caching with a long TTL: setting the hash as query makes the URL unique for the content (if the content changes). The function call
/// .Fn filewithhash "path"
/// prints `path?hash`.
/// .El
/// 
/// .Sh EXAMPLE
/// .Bd -literal
/// {{=cardfeature <- {fillHeight: true, dark: false, photoHideBreakpoint: "md"}}}
/// [[{.card{{#!dark}}; .light{{/}}}
///   [[{.card-header}
///     :fas-{{icon}}:{.mr-2}
///     {{title}}
///   ]]
///   ...
///   {{eval(body, this)}}
///   ...
///   ]
/// ]]
/// {{/cardfeature}}
/// 
/// {{cardfeature <- {
///   dark: true,
///   icon: "calendar-star",
///   title: "Upcoming",
///   body: compile(|
///     On **Tuesday 1 December** from **16:00 - 17:30**, *X* will present her recently published book 'XYZ'.
///   ),
///   buttonLeft: {icon: "sign-in", name: "join", href: "http://"},
///   buttonRight: {icon: "calendar", name: "calendar feed", href: "http://"}
/// }}}
/// .Ed
struct Options {
    /// base dir relative to which `filehash()` and `filewithhash()` works
    #[flag("-b", "--base", "base")]
    base: String,
    #[flag("-t", "--template", "file")]
    /// load templates from specified files
    template_files: Vec<String>,
    /// number of header sections to skip
    #[flag("-h", "--header")]
    headers_sections_skip: usize,
    #[allow(dead_code)]
    markdown_files: Vec<String>,
    #[flag("-e", "foo")]
    headers_section_skip: Option<FlagOption>,
    #[subcommands()]
    subcommand: Option<Sub>,
}

fn main() {
    let options : Options = commandy::parse_args();
    //let options = Options::default();
    println!("{} version {}/{}/{} [{}-{}-{}]", env::args().next().unwrap_or_default(), buildinfy::build_reference().unwrap_or(""), env!("CARGO_PKG_VERSION"), buildinfy::build_pipeline_id_per_project().unwrap_or(""), buildinfy::build_target(), buildinfy::build_revision().unwrap_or(""), buildinfy::build_pipeline_id().unwrap_or(""));
    println!("name = {}", env!("CARGO_PKG_NAME"));
    println!("base = {}", options.base);
    println!("templates = {:?}", options.template_files);
    println!("skip = {}", options.headers_sections_skip);
    println!("result: {:?}", options);
}