//! # clparse //! simple command line parser for ableOS #![no_std] extern crate alloc; #[cfg(feature = "std")] extern crate std; use hashbrown; use hashbrown::HashMap; use alloc::string::String; pub use toml; pub type Argmap = HashMap; #[derive(Debug, Clone)] pub struct Arguments { pub arguments: Argmap, } impl Arguments { // TODO: Maybe check if a key is truthy, like if it's value equals "true", "1", "on", etc. // NOTE: The above is out of scope for this pr but will be considered -able pub fn parse(command: String) -> Result { // TODO: Parse one implicit arg. // NOTE: The above is out of scope for this pr but will be considered -able let mut cmd = Arguments { arguments: HashMap::new(), }; if !command.is_empty() { let command_split = command.split(" "); for kv_pair in command_split { if kv_pair.contains("=") { // println!("{kv_pair}"); let mut kv_split = kv_pair.split("="); let key = kv_split.next().unwrap().to_string(); // println!("{key}"); let value = kv_split.next().unwrap().to_string(); // println!("{value}"); cmd.arguments.insert(key, value); } else { return Err(CLparseErrors::MissingEquals); } } } Ok(cmd) } pub fn parse_from_string>( string: S, ) -> Result<(Value, Arguments), CLparseErrors> { let config: toml::Value = Value::String("".to_string()); let args = Arguments::parse(string.into())?; return Ok((config, args)); } #[cfg(feature = "std")] pub fn parse_from_args() -> Result<(Value, Arguments), CLparseErrors> { use std::{ collections::{BTreeMap, HashMap}, env, fs::File, io::{Read, Write}, println, }; use alloc::format; let args = env::args(); let mut args_str = String::new(); let dir = env::current_dir().unwrap(); let mut config: toml::Value = Value::String("".to_string()); match env::current_exe() { Ok(bin) => { if let Some(exe_name) = bin.as_path().file_name() { let exe_name = exe_name.to_str().unwrap(); // println!("{exe_name}"); let path = format!( "/home/{}/configs/{}/config.toml", env::var("USER").unwrap(), exe_name ); let path = std::path::Path::new(&path); if path.exists() { let display = path.display(); let mut file = match File::open(&path) { Err(why) => panic!("couldn't open {}: {}", display, why), Ok(file) => file, }; let mut s = String::new(); match file.read_to_string(&mut s) { Err(why) => panic!("couldn't read {}: {}", display, why), Ok(_) => { // print!("{} contains:\n{}", display, s) } } config = toml::from_str(&s).unwrap(); } else { let mut file = File::create(path); println!("Make a file in your home dir called \"configs\""); file.unwrap().write_all(b"Hello, world!").unwrap(); } } } Err(_) => todo!(), } for (count, arg) in args.enumerate() { if count == 0 || count == 1 { // Ignore stdio + program name on unix } else { args_str.push_str(&arg); args_str.push(' '); } } // needed to remove the very last ` ` which is unneeded args_str.pop(); // println!("{args_str}"); let ret = Arguments::parse(args_str)?; return Ok((config, ret)); } pub fn is_truthy(&self, key: &str) -> bool { match self.arguments.get(key) { Some(val) => { let val = val.to_lowercase(); if val == "true" || val == "1" || val == "on" || val == "t" { return true; } false } None => false, } } } #[derive(Debug)] pub enum CLparseErrors { MalformendArgument, MissingEquals, } impl Display for CLparseErrors { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{:?}", self)?; Ok(()) } } use core::fmt::Display; use crate::alloc::string::ToString; #[cfg(test)] mod tests { use super::*; #[test] #[cfg(feature = "std")] fn test_arg_parse() { use std::println; let argline = "abc=xyz".to_string(); match Arguments::parse(argline) { Ok(ok) => { println!("{ok:?}") } Err(err) => panic!("{err:?}"), } } #[test] fn fail_test_arg_parse() { let argline = "abcxyz".to_string(); match Arguments::parse(argline) { Ok(ok) => panic!("{ok:?}"), Err(_) => {} } } } use toml::Value; #[test] #[cfg(feature = "std")] pub fn load_config() { use std::println; let toml_str = include_str!("../assets/example.toml"); let decoded: Value = toml::from_str(toml_str).unwrap(); println!("{}", decoded); }