Add basic command line boilerplate and arg parsing
This commit is contained in:
		
						commit
						2d711d2dfa
					
				|  | @ -0,0 +1 @@ | |||
| /target | ||||
|  | @ -0,0 +1,131 @@ | |||
| # This file is automatically @generated by Cargo. | ||||
| # It is not intended for manual editing. | ||||
| version = 3 | ||||
| 
 | ||||
| [[package]] | ||||
| name = "autocfg" | ||||
| version = "1.0.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "hashbrown" | ||||
| version = "0.11.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "indexmap" | ||||
| version = "1.7.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" | ||||
| dependencies = [ | ||||
|  "autocfg", | ||||
|  "hashbrown", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "linked-hash-map" | ||||
| version = "0.5.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "proc-macro2" | ||||
| version = "1.0.34" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1" | ||||
| dependencies = [ | ||||
|  "unicode-xid", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "quote" | ||||
| version = "1.0.10" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "ryu" | ||||
| version = "1.0.9" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "serde" | ||||
| version = "1.0.131" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b4ad69dfbd3e45369132cc64e6748c2d65cdfb001a2b1c232d128b4ad60561c1" | ||||
| dependencies = [ | ||||
|  "serde_derive", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "serde_derive" | ||||
| version = "1.0.131" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b710a83c4e0dff6a3d511946b95274ad9ca9e5d3ae497b63fda866ac955358d2" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "serde_yaml" | ||||
| version = "0.8.23" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "a4a521f2940385c165a24ee286aa8599633d162077a54bdcae2a6fd5a7bfa7a0" | ||||
| dependencies = [ | ||||
|  "indexmap", | ||||
|  "ryu", | ||||
|  "serde", | ||||
|  "yaml-rust", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "shepherd" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "serde", | ||||
|  "serde_yaml", | ||||
|  "toml", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "syn" | ||||
| version = "1.0.82" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "unicode-xid", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "toml" | ||||
| version = "0.5.8" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" | ||||
| dependencies = [ | ||||
|  "serde", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "unicode-xid" | ||||
| version = "0.2.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "yaml-rust" | ||||
| version = "0.4.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" | ||||
| dependencies = [ | ||||
|  "linked-hash-map", | ||||
| ] | ||||
|  | @ -0,0 +1,11 @@ | |||
| [package] | ||||
| name = "shepherd" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
| 
 | ||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||
| 
 | ||||
| [dependencies] | ||||
| serde = { version = "1.0", features = ["derive"] } | ||||
| serde_yaml = "0.8" | ||||
| toml = "0.5" | ||||
|  | @ -0,0 +1,55 @@ | |||
| use std::error::Error; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use std::env; | ||||
| use std::fs; | ||||
| use std::path::Path; | ||||
| 
 | ||||
| #[derive(Debug, PartialEq, Serialize, Deserialize)] | ||||
| /// The internal representation of the configuration file.
 | ||||
| pub struct Config { | ||||
|     pub source_dir: String, | ||||
| } | ||||
| 
 | ||||
| impl Config { | ||||
|     /// Create new Config struct with default values
 | ||||
|     ///
 | ||||
|     /// The `source_dir` field defaults to `$HOME/sources`
 | ||||
|     pub fn new() -> Config { | ||||
|         Config { | ||||
|             source_dir: format!("{}/sources", env::var("HOME").unwrap()), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Read TOML file and load values into a Config struct
 | ||||
|     ///
 | ||||
|     /// If the filename doesn't exist, read_config() will write the current struct to the given
 | ||||
|     /// config file.
 | ||||
|     pub fn read_config(&mut self, filename: &str) -> Result<(), Box<dyn Error>> { | ||||
|         // Load raw yaml file. If the file doesn't exist, create it
 | ||||
|         if std::path::Path::new(filename).exists() { | ||||
|             let raw = fs::read_to_string(filename)?; | ||||
|             // Convert string to our Config struct
 | ||||
|             let toml: Config = toml::from_str(&raw)?; | ||||
|             *self = toml; | ||||
|         } else { | ||||
|             let config: String = toml::to_string(&self)?; | ||||
|             // Get the path to the config file
 | ||||
|             let path = match Path::new(filename).parent()  { | ||||
|                 Some(x) => x, | ||||
|                 _ => Path::new(filename) | ||||
|             }; | ||||
|             // Make sure the path exists
 | ||||
|             fs::create_dir_all(path).unwrap(); | ||||
|             // Write default Config struct to file
 | ||||
|             fs::write(filename, config).expect("Couldn't write file"); | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     /// Create a string of the current loaded config.
 | ||||
|     ///
 | ||||
|     /// Useful for dumping the config
 | ||||
|     pub fn to_string(&self) -> Result<String, toml::ser::Error> { | ||||
|         toml::to_string_pretty(self) | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,134 @@ | |||
| pub mod config; | ||||
| use config::Config; | ||||
| use std::error::Error; | ||||
| use std::env; | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| /// Holds the current state of the application.
 | ||||
| ///
 | ||||
| /// This is used when processing command line arguments, and is thus loaded before the
 | ||||
| /// configuration file is.
 | ||||
| pub struct State { | ||||
|     cmd: Option<Cmd>, | ||||
|     url: Option<String>, | ||||
|     pub config: String, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| /// Used to signal which main command is going to be run
 | ||||
| enum Cmd { | ||||
|     Fetch, | ||||
|     Help, | ||||
|     DumpConfig, | ||||
| } | ||||
| 
 | ||||
| impl State { | ||||
|     /// Parses command line flags/arguments and creates the application state.
 | ||||
|     ///
 | ||||
|     /// Argument parsing goes left to right. The first not flag argument it comes across is set as
 | ||||
|     /// the cmd.
 | ||||
|     ///
 | ||||
|     /// Parsing priority is as follows:
 | ||||
|     /// - `--long-flag` type arguments
 | ||||
|     /// - `-mcsf` multi character short flag type arguments
 | ||||
|     /// - `command` type arguments
 | ||||
|     pub fn new(mut args: std::env::Args) -> State { | ||||
|         let mut state = State { | ||||
|             cmd: None, | ||||
|             url: None, | ||||
|             config: format!("{}/.config/shepherd/config.toml", env::var("HOME").unwrap()), | ||||
|         }; | ||||
| 
 | ||||
|         let mut arg = args.next(); | ||||
|         // Iterate through the command line arguments
 | ||||
|         while let Some(x) = &arg { | ||||
|             // Long arguments
 | ||||
|             if x.starts_with("--") { | ||||
|                 let option = x.strip_prefix("--").unwrap(); | ||||
|                 match option { | ||||
|                     "help" => state.cmd = Some(Cmd::Help), | ||||
|                     "dump-config" => state.cmd = Some(Cmd::DumpConfig), | ||||
|                     "config" => { | ||||
|                         let file = args.next(); | ||||
|                         match file { | ||||
|                             Some(x) => state.config = x, | ||||
|                             None => eprintln!("Expected config argument"), | ||||
|                         } | ||||
|                     } | ||||
|                     _ => {} | ||||
|                 } | ||||
|             } | ||||
|             // short arguments
 | ||||
|             else if x.starts_with("-") { | ||||
|                 let options = x.strip_prefix("-").unwrap().chars(); | ||||
|                 for opt in options { | ||||
|                     match opt { | ||||
|                         'h' => state.cmd = Some(Cmd::Help), | ||||
|                         _ => {} | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             // Fetch command
 | ||||
|             else if x == "fetch" { | ||||
|                 let url = args.next(); | ||||
|                 match url { | ||||
|                     Some(x) => match state.cmd { | ||||
|                         None => { | ||||
|                             state.cmd = Some(Cmd::Fetch); | ||||
|                             state.url = Some(x); | ||||
|                         } | ||||
|                         _ => {} | ||||
|                     }, | ||||
|                     None => { | ||||
|                         eprintln!("No URL provided for \'number\' command\n"); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             arg = args.next(); | ||||
|         } | ||||
| 
 | ||||
|         // Fall back to Help command if no other command was given
 | ||||
|         state.cmd = match state.cmd { | ||||
|             Some(x) => Some(x), | ||||
|             None => Some(Cmd::Help), | ||||
|         }; | ||||
|         state | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn run(state: State, config: Config) -> Result<(), Box<dyn Error>> { | ||||
|     match state.cmd { | ||||
|         Some(Cmd::Help) => { | ||||
|             println!("{}", help_msg()); | ||||
|         } | ||||
|         Some(Cmd::DumpConfig) => { | ||||
|             println!("{}", config.to_string().unwrap()); | ||||
|         } | ||||
|         Some(x) => { | ||||
|             println!("{:?} hasn't been implemented yet!", x) | ||||
|         } | ||||
|         _ => {} | ||||
|     } | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| fn help_msg() -> String { | ||||
|     format!( | ||||
|         "Git repository manager
 | ||||
| 
 | ||||
| USAGE: | ||||
|     shepherd [--help] <command> [<args>] | ||||
| 
 | ||||
| OPTIONS: | ||||
|     -h, --help      Print out this help message | ||||
|     --dump-config   dump the current configuration | ||||
| 
 | ||||
| COMMANDS: | ||||
| General | ||||
|     help    Print out this help message | ||||
| 
 | ||||
| Manage Repositories | ||||
|     clone   Add another git repo to keep track of | ||||
|     fetch   Update currently tracked repos" | ||||
|     ) | ||||
| } | ||||
|  | @ -0,0 +1,13 @@ | |||
| use std::env; | ||||
| use shepherd::State; | ||||
| use shepherd::config::Config; | ||||
| 
 | ||||
| fn main() { | ||||
|     let args = env::args(); | ||||
|     let state = State::new(args); | ||||
|     let mut config = Config::new(); | ||||
|     config.read_config(&state.config).unwrap(); | ||||
|     if let Err(e) = shepherd::run(state, config) { | ||||
|         eprintln!("{}", e); | ||||
|     } | ||||
| } | ||||
		Loading…
	
		Reference in New Issue