getargs-derive 0.1.0

Clap-style #[derive] command-line argument parser for no_std and embedded Rust: zero allocation, compile-time help, subcommands. Built on getargs.
Documentation
use super::{
    Node, all_command_names, command_names, longs, options, positionals, shorts, subcommands,
    summary, tree,
};
use crate::CommandInfo;
use std::string::String;
use std::vec::Vec;

fn quote(text: &str) -> String {
    let mut quoted = String::from("'");
    for character in text.chars() {
        if matches!(character, '\\' | '\'') {
            quoted.push('\\');
        }
        quoted.push(character);
    }
    quoted.push('\'');
    quoted
}

fn word(text: &str) -> String {
    let plain = !text.is_empty()
        && text.chars().all(|character| {
            character.is_ascii_alphanumeric() || matches!(character, '-' | '_' | '.')
        });
    if plain {
        String::from(text)
    } else {
        quote(text)
    }
}

fn values(values: &[&str]) -> String {
    let values: Vec<&str> = values.to_vec();
    quote(&values.join(" "))
}

fn seen(commands: &[&CommandInfo]) -> String {
    let names: Vec<String> = commands
        .iter()
        .flat_map(|command| all_command_names(command))
        .map(word)
        .collect();
    std::format!("__fish_seen_subcommand_from {}", names.join(" "))
}

fn condition(parts: &[String]) -> String {
    match parts {
        [] => String::new(),
        parts => std::format!(" -n {}", quote(&parts.join("; and "))),
    }
}

pub(super) fn render(command: &CommandInfo) -> String {
    let name = word(command.name);
    let mut output = String::new();
    for node in tree(command) {
        let scope: Vec<String> = node.path[1..]
            .iter()
            .map(|command| seen(&[command]))
            .collect();
        let mut here = scope.clone();
        if !node.command.commands.is_empty() {
            let children: Vec<&CommandInfo> = node.command.commands.iter().collect();
            here.push(std::format!("not {}", seen(&children)));
        }
        let own = Node {
            path: node.path.clone(),
            command: node.command,
            args: node.command.args.iter().collect(),
        };
        render_node(
            &mut output,
            &name,
            &own,
            &condition(&scope),
            &condition(&here),
        );
        for child in subcommands(node.command) {
            for child_name in command_names(child) {
                let mut line = std::format!(
                    "complete -c {name}{} -f -a {}",
                    condition(&here),
                    word(child_name)
                );
                let help = summary(child.about);
                if !help.is_empty() {
                    line.push_str(&std::format!(" -d {}", quote(help)));
                }
                output.push_str(&line);
                output.push('\n');
            }
        }
    }
    output
}

fn render_node(output: &mut String, name: &str, node: &Node, scope: &str, here: &str) {
    for arg in options(node) {
        let condition = if arg.global { scope } else { here };
        let mut line = std::format!("complete -c {name}{condition}");
        for short in shorts(arg) {
            line.push_str(&std::format!(" -s {}", word(&String::from(short))));
        }
        for long in longs(arg) {
            line.push_str(&std::format!(" -l {}", word(long)));
        }
        let help = summary(arg.help);
        if !help.is_empty() {
            line.push_str(&std::format!(" -d {}", quote(help)));
        }
        if arg.takes_value() {
            line.push_str(" -r");
            if !arg.possible_values.is_empty() {
                line.push_str(&std::format!(" -f -a {}", values(arg.possible_values)));
            }
        }
        output.push_str(&line);
        output.push('\n');
    }
    for arg in positionals(node).filter(|arg| !arg.possible_values.is_empty()) {
        let mut line = std::format!(
            "complete -c {name}{here} -a {}",
            values(arg.possible_values)
        );
        let help = summary(arg.help);
        if !help.is_empty() {
            line.push_str(&std::format!(" -d {}", quote(help)));
        }
        output.push_str(&line);
        output.push('\n');
    }
}