Listen to git commits for a specific repo and run a shell command
at master 6.9 kB view raw
1fn help() { 2 println!( 3 "Help: tangled-on-commit 4Listen for commits on a specified repository and execute a shell command. 5 6CLI Arguments: 7 `tangled-on-commit (-h | --help)` 8 Displays this message 9 10 `tangled-on-commit` 11 No specified handle, repo, or command. Falls back to config/env 12 13 `tangled-on-commit SHELL` 14 Uses config/env for handle and repo 15 16 `tangled-on-commit @HANDLE SHELL` 17 Uses config/env for repo 18 19 `tangled-on-commit REPO SHELL` 20 Uses config/env for handle 21 22 `tangled-on-commit @HANDLE/REPO SHELL` 23 `tangled-on-commit HANDLE REPO SHELL` 24 No config/env 25 26JSON: 27 Loads the file `tangled-on-commit.json` from cwd if it exists. 28 Reads keys \"handle\", \"repo_name\", and \"shell\". 29 Unknown keys are ignored and any key can be ommitted 30 JSON is used if the arguments aren't passed to the CLI 31 32Env: 33 Loads the environment variables `TANGLED_ON_COMMIT_HANDLE` and `TANGLED_ON_COMMIT_REPO_NAME` 34 Shell cannot be set by environment variables. 35 Env variables are used if relevant keys are ommitted an arguments aren't passed to the CLI. 36" 37 ) 38} 39 40#[derive(Debug)] 41pub struct Config { 42 pub handle: String, 43 pub repo_name: String, 44 pub shell: String, 45} 46 47pub fn load_config() -> Result<Config, ()> { 48 // load config from cli args if present 49 // if omitted, fallback to a local `tangled-on-commit.json` file 50 // if key omitted or file not found, fall back to env 51 // if env omitted, error out and quit 52 // note: shell is not loaded from env (to avoid the user unknowingly executing scripts) 53 54 // if any args are `-h` || `--help` display help and quit 55 for arg in std::env::args() { 56 if arg == "-h" || arg == "--help" { 57 help(); 58 return Err(()); 59 } 60 } 61 62 // if 0 args are passed, skip 63 // if 1 arg is passed, its the shell command 64 // if 2 args are passed: 65 // if arg 1 starts in @ its a handle 66 // if it contains a / the contents after the slash is the reponame 67 // else its the reponame 68 // arg 2 is always the shell command 69 // if 3 args are passed its handle repo shell 70 // if more args are passed its an error 71 72 let mut handle: Option<String> = None; 73 let mut repo_name: Option<String> = None; 74 let mut shell: Option<String> = None; 75 match std::env::args().collect::<Vec<_>>().len() { 76 // 0 args (std env args includes this script) 77 1 => {} 78 2 => { 79 shell = Some( 80 std::env::args() 81 .last() 82 .expect("Invalid state: 2 `Some` std::env::args() but found no Some values"), 83 ) 84 } 85 3 => { 86 // load args and consume first 87 let mut args = std::env::args(); 88 args.next(); 89 90 if let Some(val) = args.next() { 91 if val.starts_with("@") { 92 if val.contains("/") { 93 let entries: Vec<_> = val.split("/").collect(); 94 if entries.len() != 2 { 95 println!("Invalid argument: `{}` is malformed", val); 96 return Err(()); 97 } 98 handle = Some(entries[0][1..].to_string()); 99 repo_name = Some(entries[1].to_string()); 100 } else { 101 handle = Some(val[1..].to_string()); 102 } 103 } else { 104 repo_name = Some(val) 105 }; 106 } 107 shell = Some( 108 args.next() 109 .expect("Invalid state: 3 `Some` std::env::args() but only found 2"), 110 ); 111 } 112 4 => { 113 // load args and consume first 114 let mut args = std::env::args(); 115 args.next(); 116 117 handle = Some( 118 args.next() 119 .expect("Invalid state: 4 `Some` std::env::args() but only found 1"), 120 ); 121 repo_name = Some( 122 args.next() 123 .expect("Invalid state: 4 `Some` std::env::args() but only found 2"), 124 ); 125 shell = Some( 126 args.next() 127 .expect("Invalid state: 4 `Some` std::env::args() but only found 3"), 128 ); 129 } 130 _ => { 131 // err 132 } 133 } 134 135 if let Some(ref handle) = handle 136 && let Some(ref repo_name) = repo_name 137 && let Some(ref shell) = shell 138 { 139 return Ok(Config { 140 handle: handle.to_string(), 141 repo_name: repo_name.to_string(), 142 shell: shell.to_string(), 143 }); 144 } 145 146 // now load config 147 if let Ok(file) = std::fs::read_to_string("./tangled-on-commit.json") { 148 if let Ok(parsed) = json::parse(&file) { 149 if handle.is_none() 150 && let Some(json_handle) = parsed["handle"].as_str() 151 { 152 handle = Some(String::from(json_handle)) 153 } 154 if repo_name.is_none() 155 && let Some(json_repo_name) = parsed["repo_name"].as_str() 156 { 157 repo_name = Some(String::from(json_repo_name)) 158 } 159 if shell.is_none() 160 && let Some(json_shell) = parsed["shell"].as_str() 161 { 162 shell = Some(String::from(json_shell)) 163 } 164 } 165 } 166 167 if let Some(ref handle) = handle 168 && let Some(ref repo_name) = repo_name 169 && let Some(ref shell) = shell 170 { 171 return Ok(Config { 172 handle: handle.to_string(), 173 repo_name: repo_name.to_string(), 174 shell: shell.to_string(), 175 }); 176 } 177 178 // now load from env 179 if handle.is_none() 180 && let Ok(env_handle) = std::env::var("TANGLED_ON_COMMIT_HANDLE") 181 { 182 handle = Some(String::from(env_handle)) 183 } 184 if repo_name.is_none() 185 && let Ok(env_repo_name) = std::env::var("TANGLED_ON_COMMIT_REPO_NAME") 186 { 187 repo_name = Some(String::from(env_repo_name)) 188 } 189 190 if let Some(ref handle) = handle 191 && let Some(ref repo_name) = repo_name 192 && let Some(ref shell) = shell 193 { 194 return Ok(Config { 195 handle: handle.to_string(), 196 repo_name: repo_name.to_string(), 197 shell: shell.to_string(), 198 }); 199 } 200 201 // couldnt resolve every value 202 // print an error and quit 203 println!("Unable to find a value for every setting. Missing values for:"); 204 if handle.is_none() { 205 println!("- handle"); 206 } 207 if repo_name.is_none() { 208 println!("- repo_name"); 209 } 210 if shell.is_none() { 211 println!("- shell"); 212 } 213 println!("\nRun `tangled-on-commit --help` to see the help message"); 214 return Err(()); 215}