xee 0.1.6

Xee CLI. Swiss Army knife for XML. XPath and XSLT
use ahash::HashMap;

use crate::repl::RunContext;

type Execute = Box<dyn Fn(&[&str], &mut RunContext, &CommandDefinitions)>;

pub(crate) struct CommandDefinition {
    name: &'static str,
    short_name: Option<&'static str>,
    about: &'static str,
    args: Vec<ArgumentDefinition>,
    execute: Execute,
}

#[derive(Default)]
pub(crate) struct CommandDefinitions {
    pub(crate) definitions: Vec<CommandDefinition>,
    by_name: HashMap<&'static str, usize>,
    by_short_name: HashMap<&'static str, usize>,
}

pub(crate) struct ArgumentDefinition {
    name: &'static str,
    default: Option<&'static str>,
}

impl ArgumentDefinition {
    pub fn new(name: &'static str, default: Option<&'static str>) -> Self {
        Self { name, default }
    }
}

impl CommandDefinitions {
    pub(crate) fn new(defitions: Vec<CommandDefinition>) -> Self {
        let mut definitions = Self {
            definitions: Vec::new(),
            by_name: HashMap::default(),
            by_short_name: HashMap::default(),
        };
        for definition in defitions {
            definitions.add(definition);
        }
        definitions
    }

    pub(crate) fn add(&mut self, definition: CommandDefinition) {
        let index = self.definitions.len();
        self.by_name.insert(definition.name, index);
        if let Some(short_name) = definition.short_name {
            self.by_short_name.insert(short_name, index);
        }
        self.definitions.push(definition);
    }

    pub(crate) fn execute(&self, command: &str, run_context: &mut RunContext) {
        let parts = command.split_whitespace().collect::<Vec<_>>();
        let command_s = parts[0];
        let args = &parts[1..];
        let command = self.get(command_s);
        if let Some(command) = command {
            if args.len() > command.args.len() {
                println!("Too many arguments for command: {}", command_s);
                return;
            }
            let args = command.preprocess_arguments(args);

            if args.len() < command.args.len() {
                println!("Too few arguments for command: {}", command_s);
                return;
            }
            (command.execute)(&args, run_context, self);
        } else {
            println!("Unknown command: {}", command_s);
        }
    }

    fn get(&self, command: &str) -> Option<&CommandDefinition> {
        self.by_name
            .get(command)
            .or_else(|| self.by_short_name.get(command))
            .map(|&i| &self.definitions[i])
    }
}

impl CommandDefinition {
    pub(crate) fn new(
        name: &'static str,
        short_name: Option<&'static str>,
        about: &'static str,
        args: Vec<ArgumentDefinition>,
        execute: Execute,
    ) -> Self {
        Self {
            name,
            short_name,
            about,
            args,
            execute,
        }
    }

    pub(crate) fn help(&self) -> String {
        let description = self.arg_description();
        let main = if description.is_empty() {
            format!("!{} - {}", self.name, self.about)
        } else {
            format!("!{} {} - {}", self.name, description, self.about)
        };
        if let Some(short_name) = self.short_name {
            format!("{} (!{})", main, short_name)
        } else {
            main
        }
    }

    fn arg_description(&self) -> String {
        self.args
            .iter()
            .map(|arg| {
                if let Some(default) = arg.default {
                    format!("<{}>={}", arg.name, default)
                } else {
                    format!("<{}>", arg.name)
                }
            })
            .collect::<Vec<_>>()
            .join(" ")
    }

    fn preprocess_arguments<'a>(&self, args: &[&'a str]) -> Vec<&'a str> {
        let mut result = Vec::with_capacity(self.args.len());
        let mut i = 0;
        for arg in &self.args {
            if i < args.len() {
                result.push(args[i]);
                i += 1;
            } else if let Some(default) = arg.default {
                result.push(default);
            }
        }
        result
    }
}