fn help() { println!( "Help: tangled-on-commit Listen for commits on a specified repository and execute a shell command. CLI Arguments: `tangled-on-commit (-h | --help)` Displays this message `tangled-on-commit` No specified handle, repo, or command. Falls back to config/env `tangled-on-commit SHELL` Uses config/env for handle and repo `tangled-on-commit @HANDLE SHELL` Uses config/env for repo `tangled-on-commit REPO SHELL` Uses config/env for handle `tangled-on-commit @HANDLE/REPO SHELL` `tangled-on-commit HANDLE REPO SHELL` No config/env JSON: Loads the file `tangled-on-commit.json` from cwd if it exists. Reads keys \"handle\", \"repo_name\", and \"shell\". Unknown keys are ignored and any key can be ommitted JSON is used if the arguments aren't passed to the CLI Env: Loads the environment variables `TANGLED_ON_COMMIT_HANDLE` and `TANGLED_ON_COMMIT_REPO_NAME` Shell cannot be set by environment variables. Env variables are used if relevant keys are ommitted an arguments aren't passed to the CLI. " ) } #[derive(Debug)] pub struct Config { pub handle: String, pub repo_name: String, pub shell: String, } pub fn load_config() -> Result { // load config from cli args if present // if omitted, fallback to a local `tangled-on-commit.json` file // if key omitted or file not found, fall back to env // if env omitted, error out and quit // note: shell is not loaded from env (to avoid the user unknowingly executing scripts) // if any args are `-h` || `--help` display help and quit for arg in std::env::args() { if arg == "-h" || arg == "--help" { help(); return Err(()); } } // if 0 args are passed, skip // if 1 arg is passed, its the shell command // if 2 args are passed: // if arg 1 starts in @ its a handle // if it contains a / the contents after the slash is the reponame // else its the reponame // arg 2 is always the shell command // if 3 args are passed its handle repo shell // if more args are passed its an error let mut handle: Option = None; let mut repo_name: Option = None; let mut shell: Option = None; match std::env::args().collect::>().len() { // 0 args (std env args includes this script) 1 => {} 2 => { shell = Some( std::env::args() .last() .expect("Invalid state: 2 `Some` std::env::args() but found no Some values"), ) } 3 => { // load args and consume first let mut args = std::env::args(); args.next(); if let Some(val) = args.next() { if val.starts_with("@") { if val.contains("/") { let entries: Vec<_> = val.split("/").collect(); if entries.len() != 2 { println!("Invalid argument: `{}` is malformed", val); return Err(()); } handle = Some(entries[0][1..].to_string()); repo_name = Some(entries[1].to_string()); } else { handle = Some(val[1..].to_string()); } } else { repo_name = Some(val) }; } shell = Some( args.next() .expect("Invalid state: 3 `Some` std::env::args() but only found 2"), ); } 4 => { // load args and consume first let mut args = std::env::args(); args.next(); handle = Some( args.next() .expect("Invalid state: 4 `Some` std::env::args() but only found 1"), ); repo_name = Some( args.next() .expect("Invalid state: 4 `Some` std::env::args() but only found 2"), ); shell = Some( args.next() .expect("Invalid state: 4 `Some` std::env::args() but only found 3"), ); } _ => { // err } } if let Some(ref handle) = handle && let Some(ref repo_name) = repo_name && let Some(ref shell) = shell { return Ok(Config { handle: handle.to_string(), repo_name: repo_name.to_string(), shell: shell.to_string(), }); } // now load config if let Ok(file) = std::fs::read_to_string("./tangled-on-commit.json") { if let Ok(parsed) = json::parse(&file) { if handle.is_none() && let Some(json_handle) = parsed["handle"].as_str() { handle = Some(String::from(json_handle)) } if repo_name.is_none() && let Some(json_repo_name) = parsed["repo_name"].as_str() { repo_name = Some(String::from(json_repo_name)) } if shell.is_none() && let Some(json_shell) = parsed["shell"].as_str() { shell = Some(String::from(json_shell)) } } } if let Some(ref handle) = handle && let Some(ref repo_name) = repo_name && let Some(ref shell) = shell { return Ok(Config { handle: handle.to_string(), repo_name: repo_name.to_string(), shell: shell.to_string(), }); } // now load from env if handle.is_none() && let Ok(env_handle) = std::env::var("TANGLED_ON_COMMIT_HANDLE") { handle = Some(String::from(env_handle)) } if repo_name.is_none() && let Ok(env_repo_name) = std::env::var("TANGLED_ON_COMMIT_REPO_NAME") { repo_name = Some(String::from(env_repo_name)) } if let Some(ref handle) = handle && let Some(ref repo_name) = repo_name && let Some(ref shell) = shell { return Ok(Config { handle: handle.to_string(), repo_name: repo_name.to_string(), shell: shell.to_string(), }); } // couldnt resolve every value // print an error and quit println!("Unable to find a value for every setting. Missing values for:"); if handle.is_none() { println!("- handle"); } if repo_name.is_none() { println!("- repo_name"); } if shell.is_none() { println!("- shell"); } println!("\nRun `tangled-on-commit --help` to see the help message"); return Err(()); }