forked from josh-project/josh
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This simplifies the argument parsing a lot - we can almost parse the needed structure just with that, including all the manual default handling. The only thing that does change is the handling of upstreams - rather than populating two fields in the Arg struct, it now contains a `Vec<Remote>`. We can use clap to ensure there's at least one element (same for local), but comparison of individual args and further validation (max 2, not two of the same type) is now left to `josh-proxy.rs`. For this, a `make_upstream` is introduced, turning that list of enums with URLs into a JoshProxyUpstream. Error handling in run_proxy isn't awfully verbose, just exit nonzero, so I opted to log *that* specific error with an eprintln!, but happy to also add it for all errors (in main()). This is in preparation for josh-project#1288.
- Loading branch information
Showing
2 changed files
with
74 additions
and
173 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,175 +1,52 @@ | ||
use josh::{josh_error, JoshResult}; | ||
#[derive(Clone, Debug)] | ||
pub enum Remote { | ||
Http(String), | ||
Ssh(String), | ||
} | ||
|
||
pub struct Remote { | ||
pub http: Option<String>, | ||
pub ssh: Option<String>, | ||
fn parse_remote(s: &str) -> Result<Remote, &'static str> { | ||
match s { | ||
s if s.starts_with("http://") || s.starts_with("https://") => { | ||
Ok(Remote::Http(s.to_string())) | ||
} | ||
s if s.starts_with("ssh://") => Ok(Remote::Ssh(s.to_string())), | ||
_ => return Err("unsupported scheme"), | ||
} | ||
} | ||
|
||
#[derive(clap::Parser, Debug)] | ||
#[command(name = "josh-proxy")] | ||
pub struct Args { | ||
pub remote: Remote, | ||
pub local: String, | ||
#[arg(long, required = true, value_parser = parse_remote)] | ||
pub remote: Vec<Remote>, | ||
#[arg(long, required = true)] | ||
pub local: Option<String>, | ||
#[arg(name = "poll", long)] | ||
pub poll_user: Option<String>, | ||
#[arg(long, help = "Run git gc during maintenance")] | ||
pub gc: bool, | ||
#[arg(long)] | ||
pub require_auth: bool, | ||
#[arg(long)] | ||
pub no_background: bool, | ||
|
||
#[arg( | ||
short, | ||
help = "DEPRECATED - no effect! Number of concurrent upstream git fetch/push operations" | ||
)] | ||
_n: Option<String>, | ||
|
||
#[arg(long, default_value = "8000")] | ||
pub port: u16, | ||
#[arg( | ||
short, | ||
default_value = "0", | ||
help = "Duration between forced cache refresh" | ||
)] | ||
#[arg(long, short)] | ||
pub cache_duration: u64, | ||
#[arg(long, help = "Duration between forced cache refresh")] | ||
pub static_resource_proxy_target: Option<String>, | ||
#[arg(long, help = "Filter to be prefixed to all queries of this instance")] | ||
pub filter_prefix: Option<String>, | ||
} | ||
|
||
fn parse_int<T: std::str::FromStr>( | ||
matches: &clap::ArgMatches, | ||
arg_name: &str, | ||
default: Option<T>, | ||
) -> JoshResult<T> | ||
where | ||
<T as std::str::FromStr>::Err: std::fmt::Display, | ||
{ | ||
let arg = matches.get_one::<String>(arg_name).map(|s| s.as_str()); | ||
|
||
let arg = match (arg, default) { | ||
(None, None) => { | ||
return Err(josh_error(&format!( | ||
"missing required argument: {}", | ||
arg_name | ||
))) | ||
} | ||
(None, Some(default)) => Ok(default), | ||
(Some(value), _) => value.parse::<T>(), | ||
}; | ||
|
||
arg.map_err(|e| josh_error(&format!("error parsing argument {}: {}", arg_name, e))) | ||
} | ||
|
||
fn make_command() -> clap::Command { | ||
clap::Command::new("josh-proxy") | ||
.arg( | ||
clap::Arg::new("remote") | ||
.long("remote") | ||
.action(clap::ArgAction::Append), | ||
) | ||
.arg(clap::Arg::new("local").long("local")) | ||
.arg(clap::Arg::new("poll").long("poll")) | ||
.arg( | ||
clap::Arg::new("gc") | ||
.long("gc") | ||
.action(clap::ArgAction::SetTrue) | ||
.help("Run git gc during maintenance"), | ||
) | ||
.arg( | ||
clap::Arg::new("require-auth") | ||
.long("require-auth") | ||
.action(clap::ArgAction::SetTrue), | ||
) | ||
.arg( | ||
clap::Arg::new("no-background") | ||
.long("no-background") | ||
.action(clap::ArgAction::SetTrue), | ||
) | ||
.arg(clap::Arg::new("n").short('n').help( | ||
"DEPRECATED - no effect! Number of concurrent upstream git fetch/push operations", | ||
)) | ||
.arg(clap::Arg::new("port").long("port")) | ||
.arg( | ||
clap::Arg::new("cache-duration") | ||
.long("cache-duration") | ||
.short('c') | ||
.help("Duration between forced cache refresh"), | ||
) | ||
.arg( | ||
clap::Arg::new("static-resource-proxy-target") | ||
.long("static-resource-proxy-target") | ||
.help("Proxy static resource requests to a different URL"), | ||
) | ||
.arg( | ||
clap::Arg::new("filter-prefix") | ||
.long("filter-prefix") | ||
.help("Filter to be prefixed to all queries of this instance"), | ||
) | ||
} | ||
|
||
fn parse_remotes(values: &[String]) -> JoshResult<Remote> { | ||
let mut result = Remote { | ||
http: None, | ||
ssh: None, | ||
}; | ||
|
||
for value in values { | ||
match value { | ||
v if v.starts_with("http://") || v.starts_with("https://") => { | ||
result.http = match result.http { | ||
None => Some(v.clone()), | ||
Some(v) => return Err(josh_error(&format!("HTTP remote already set: {}", v))), | ||
}; | ||
} | ||
v if v.starts_with("ssh://") => { | ||
result.ssh = match result.ssh { | ||
None => Some(v.clone()), | ||
Some(v) => return Err(josh_error(&format!("SSH remote already set: {}", v))), | ||
}; | ||
} | ||
_ => { | ||
return Err(josh_error(&format!( | ||
"Unsupported remote protocol: {}", | ||
value | ||
))) | ||
} | ||
} | ||
} | ||
|
||
Ok(result) | ||
} | ||
|
||
pub fn parse_args() -> josh::JoshResult<Args> { | ||
let args = make_command().get_matches_from(std::env::args()); | ||
|
||
let remote = args | ||
.get_many::<String>("remote") | ||
.ok_or(josh_error("no remote specified"))? | ||
.cloned() | ||
.collect::<Vec<_>>(); | ||
let remote = parse_remotes(&remote)?; | ||
|
||
let local = args | ||
.get_one::<String>("local") | ||
.ok_or(josh_error("missing local directory"))? | ||
.clone(); | ||
|
||
let poll_user = args.get_one::<String>("poll").map(String::clone); | ||
let port = parse_int::<u16>(&args, "port", Some(8000))?; | ||
let cache_duration = parse_int::<u64>(&args, "cache-duration", Some(0))?; | ||
let static_resource_proxy_target = args | ||
.get_one::<String>("static-resource-proxy-target") | ||
.map(String::clone); | ||
|
||
let filter_prefix = args.get_one::<String>("filter-prefix").map(String::clone); | ||
|
||
Ok(Args { | ||
remote, | ||
local, | ||
poll_user, | ||
gc: args.get_flag("gc"), | ||
require_auth: args.get_flag("require-auth"), | ||
no_background: args.get_flag("no-background"), | ||
port, | ||
cache_duration, | ||
static_resource_proxy_target, | ||
filter_prefix, | ||
}) | ||
} | ||
|
||
pub fn parse_args_or_exit(code: i32) -> Args { | ||
match parse_args() { | ||
Err(e) => { | ||
eprintln!("Argument parsing error: {}", e.0); | ||
std::process::exit(code); | ||
} | ||
Ok(args) => args, | ||
} | ||
} | ||
|
||
#[test] | ||
fn verify_app() { | ||
make_command().debug_assert(); | ||
} |