clipanion-core 0.8.1

A simple but powerful CLI framework
Documentation
use std::collections::{HashMap, HashSet};

use crate::{actions::{Check, Reducer}, node::Node, shared::{is_terminal_node, Arg, CUSTOM_NODE_ID, ERROR_NODE_ID, INITIAL_NODE_ID, SUCCESS_NODE_ID}, transition::Transition};

#[derive(Debug, Default)]
pub struct MachineContext {
    pub preferred_names: HashMap<String, String>,
    pub valid_bindings: HashSet<String>,
}

pub struct Machine {
    pub contexts: Vec<MachineContext>,
    pub nodes: Vec<Node>,
}

impl std::fmt::Debug for Machine {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        for (id, node) in self.nodes.iter().enumerate() {
            writeln!(f, "Node {}:", id)?;

            if id == ERROR_NODE_ID {
                writeln!(f, "  [Error]")?;
            } else if id == SUCCESS_NODE_ID {
                writeln!(f, "  [Success]")?;
            }

            for (check, transition) in node.dynamics.iter() {
                writeln!(f, "  Dynamic: {:?} -> {}", check, transition.to)?;
            }

            for transition in node.shortcuts.iter() {
                writeln!(f, "  Shortcut -> {}", transition.to)?;
            }

            for (segment, transitions) in node.statics.iter() {
                for transition in transitions.iter() {
                    writeln!(f, "  Static: {:?} -> {}", segment, transition.to)?;
                }
            }
        }

        Ok(())
    }
}

impl Default for Machine {
    fn default() -> Machine {
        let mut default = Machine {
            contexts: vec![MachineContext::default()],
            nodes: vec![],
        };

        for _ in 0..CUSTOM_NODE_ID {
            default.nodes.push(Node::new());
        }

        default
    }
}

impl Machine {
    pub fn new() -> Machine {
        Default::default()
    }

    pub fn new_any_of<I>(machines: I) -> Machine where I: IntoIterator<Item = Machine> {
        let mut out = Machine::new();

        for machine in machines {
            let context_offset = out.contexts.len();
            let node_offset = out.nodes.len();

            out.contexts.extend(machine.contexts);
            out.register_shortcut(INITIAL_NODE_ID, node_offset, Reducer::None);

            for id in 0..machine.nodes.len() {
                if !is_terminal_node(id) {
                    let mut cloned_node = machine.nodes[id].clone_to_offset(node_offset);
                    cloned_node.context += context_offset;
                    out.nodes.push(cloned_node);
                }
            }
        }

        out
    }

    pub fn inject_node(&mut self, node: Node) -> usize {
        self.nodes.push(node);
        self.nodes.len() - 1
    }

    pub fn register_dynamic(&mut self, from: usize, check: Check, to: usize, reducer: Reducer) {
        self.nodes[from].dynamics.push((check, Transition::new(to, reducer)));
    }

    pub fn register_shortcut(&mut self, from: usize, to: usize, reducer: Reducer) {
        self.nodes[from].shortcuts.push(Transition::new(to, reducer));
    }

    pub fn register_static(&mut self, from: usize, key: Arg, to: usize, reducer: Reducer) {
        self.nodes[from].statics.entry(key).or_default().push(Transition::new(to, reducer));
    }

    pub fn simplify_machine(&mut self) {
        let mut visited = HashSet::new();
        let mut queue = vec![INITIAL_NODE_ID];

        while let Some(node) = queue.pop() {
            if !visited.insert(node) {
                continue;
            }

            let mut node_def = std::mem::take(&mut self.nodes[node]);

            for (_, transition) in node_def.dynamics.iter() {
                queue.push(transition.to);
            }

            for transition in node_def.shortcuts.iter() {
                queue.push(transition.to);
            }

            for (_, transitions) in node_def.statics.iter() {
                for transition in transitions.iter() {
                    queue.push(transition.to);
                }
            }

            let mut shortcuts: HashSet<usize>
                = HashSet::from_iter(node_def.shortcuts.iter().map(|t| t.to));

            while let Some(Transition {to, ..}) = node_def.shortcuts.pop() {
                let to_def = self.nodes[to].clone();

                for (segment, transitions) in to_def.statics.iter() {
                    let store
                        = node_def.statics.entry(segment.clone()).or_default();

                    for transition in transitions {
                        if !store.iter().any(|t| t.to == transition.to) {
                            store.push(transition.clone());
                        }
                    }
                }

                for (check, transition) in to_def.dynamics.iter() {
                    if !node_def.dynamics.iter().any(|(c, t)| c == check && t.to == transition.to) {
                        node_def.dynamics.push((check.clone(), transition.clone()));
                    }
                }

                for transition in to_def.shortcuts.iter() {
                    if !shortcuts.contains(&transition.to) {
                        node_def.shortcuts.push(transition.clone());
                        shortcuts.insert(transition.to);
                    }
                }
            }

            self.nodes[node] = std::mem::take(&mut node_def);
        }
    }
}