summaryrefslogblamecommitdiff
path: root/src/switchboard.rs
blob: 7aff7fda4fe61dabf8ba50e7aed67596f08177f8 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17















                                                                     
                                      








                                                  
                                    





                                                    
                                                                    
                                                                           
                                             
                  




                                                                                       
                                                                           
                                             
                                                              


                                                                                       






                                                                       
















































                                                                         
                                                         
                                                           
                                                              
                                                               
                                                     
                                                                         

                                                                  



                              
                                                    
















                                                                  
use crate::*;

use std::collections::{HashMap, VecDeque};


pub struct Switchboard {
    // First positional argument.
    pub program: String,
    // Positional arguments, stored in reverse order.
    pub positional: Vec<String>,
    // Named arguments, stored in forward order.
    pub switches: Vec<(SwitchName, Option<String>)>,
    // All arguments following a '--' token, stored in forward order.
    pub unprocessed: Option<Vec<String>>,
    // All queried values.
    pub values: HashMap<String, QueriedValue>,
    // All query errors
    pub errors: Vec<SwitchboardError>,
    // Total number of popped positional arguments
    pub i: usize,
}

impl Switchboard {
    pub fn parse(args: Vec<String>) -> Self {
        let mut positional = Vec::new();
        let mut switches = Vec::new();
        let mut unprocessed = None;
        let mut args = VecDeque::from(args);
        let mut errors = Vec::new();

        while let Some(arg) = args.pop_front() {
            if arg.is_empty() {
                continue;
            } else if arg == "--" {
                unprocessed = Some(Vec::from(args));
                break;
            } else if let Some(stripped) = arg.strip_prefix("--") {
                let (name, value) = match stripped.split_once("=") {
                    Some((name, "")) => (name, None),
                    Some((name, value)) => (name, Some(value.to_string())),
                    None => (stripped, None),
                };
                match !name.is_empty() {
                    true => switches.push((SwitchName::Long(name.to_string()), value)),
                    false => errors.push(SwitchboardError::Malformed(arg.to_string())),
                }
            } else if let Some(stripped) = arg.strip_prefix("-") {
                let (name, value) = match stripped.split_once("=") {
                    Some((name, "")) => (name, None),
                    Some((name, value)) => (name, Some(value.to_string())),
                    None => (stripped, None),
                };
                let chars: Vec<char> = name.chars().collect();
                match chars.len() == 1 {
                    true => switches.push((SwitchName::Short(chars[0]), value)),
                    false => errors.push(SwitchboardError::Malformed(arg.to_string())),
                }
            } else {
                positional.push(arg)
            }
        }
        // Reverse order of positional arguments and move program name.
        positional.reverse();
        let program = positional.pop().unwrap();
        let values = HashMap::new();
        let i = 0;
        Self {
            program, positional, switches, unprocessed, values, errors, i
        }
    }

    pub fn from_env() -> Self {
        let mut args = Vec::new();
        for arg_os in std::env::args_os() {
            args.push(arg_os.to_string_lossy().to_string());
        }
        Self::parse(args)
    }

    pub fn named(&mut self, name: &str) -> NamedSwitchQuery {
        validate_name(name);
        NamedSwitchQuery {
            switchboard: self,
            name: name.to_string(),
            short: None,
            quick: None,
            doc: None,
            committed: false,
        }
    }

    pub fn positional(&mut self, name: &str) -> PositionalSwitchQuery {
        validate_name(name);
        PositionalSwitchQuery {
            switchboard: self,
            name: name.to_string(),
            doc: None,
            committed: false,
        }
    }

    pub fn raise_errors(&self) {
        if self.errors.is_empty()
        && self.switches.is_empty()
        && self.positional.is_empty(){
            return;
        }
        for (name, _value) in &self.switches {
            error!("The switch {name} was not recognised")
        }
        if !self.positional.is_empty() {
            error!("Too many positional arguments provided")
        }
        for error in &self.errors {
            match error {
                SwitchboardError::MissingNamed(name) => {
                    error!("The {name} switch is required")
                }
                SwitchboardError::MissingPositional(name) => {
                    error!("The <{name}> argument is required")
                }
                SwitchboardError::Repeated(name) => {
                    error!("The {name} switch was passed multiple times")
                }
                SwitchboardError::Malformed(string) => {
                    error!("The '{string}' argument is malformed")
                }
            }
        }
        std::process::exit(1);
    }

    pub fn get(&self, name: &str) -> &QueriedValue {
        match self.values.get(name) {
            Some(value) => value,
            None => panic!("Name has not been defined: {name:?}"),
        }
    }

    /// Check the next positional argument without consuming it.
    pub fn peek(&self) -> Option<&str> {
        self.positional.last().map(|p| p.as_str())
    }

    /// Consume the next positional argument.
    pub fn pop(&mut self) -> Option<String> {
        self.i += 1;
        self.positional.pop()
    }
}