|
1 |
| -use josh::{josh_error, JoshResult}; |
| 1 | +#[derive(Clone, Debug)] |
| 2 | +pub enum Remote { |
| 3 | + Http(String), |
| 4 | + Ssh(String), |
| 5 | +} |
2 | 6 |
|
3 |
| -pub struct Remote { |
4 |
| - pub http: Option<String>, |
5 |
| - pub ssh: Option<String>, |
| 7 | +fn parse_remote(s: &str) -> Result<Remote, &'static str> { |
| 8 | + match s { |
| 9 | + s if s.starts_with("http://") || s.starts_with("https://") => { |
| 10 | + Ok(Remote::Http(s.to_string())) |
| 11 | + } |
| 12 | + s if s.starts_with("ssh://") => Ok(Remote::Ssh(s.to_string())), |
| 13 | + _ => return Err("unsupported scheme"), |
| 14 | + } |
6 | 15 | }
|
7 | 16 |
|
| 17 | +#[derive(clap::Parser, Debug)] |
| 18 | +#[command(name = "josh-proxy")] |
8 | 19 | pub struct Args {
|
9 |
| - pub remote: Remote, |
10 |
| - pub local: String, |
| 20 | + #[arg(long, required = true, value_parser = parse_remote)] |
| 21 | + pub remote: Vec<Remote>, |
| 22 | + #[arg(long, required = true)] |
| 23 | + pub local: Option<String>, |
| 24 | + #[arg(name = "poll", long)] |
11 | 25 | pub poll_user: Option<String>,
|
| 26 | + #[arg(long, help = "Run git gc during maintenance")] |
12 | 27 | pub gc: bool,
|
| 28 | + #[arg(long)] |
13 | 29 | pub require_auth: bool,
|
| 30 | + #[arg(long)] |
14 | 31 | pub no_background: bool,
|
| 32 | + |
| 33 | + #[arg( |
| 34 | + short, |
| 35 | + help = "DEPRECATED - no effect! Number of concurrent upstream git fetch/push operations" |
| 36 | + )] |
| 37 | + _n: Option<String>, |
| 38 | + |
| 39 | + #[arg(long, default_value = "8000")] |
15 | 40 | pub port: u16,
|
| 41 | + #[arg( |
| 42 | + short, |
| 43 | + default_value = "0", |
| 44 | + help = "Duration between forced cache refresh" |
| 45 | + )] |
| 46 | + #[arg(long, short)] |
16 | 47 | pub cache_duration: u64,
|
| 48 | + #[arg(long, help = "Proxy static resource requests to a different URL")] |
17 | 49 | pub static_resource_proxy_target: Option<String>,
|
| 50 | + #[arg(long, help = "Filter to be prefixed to all queries of this instance")] |
18 | 51 | pub filter_prefix: Option<String>,
|
19 | 52 | }
|
20 |
| - |
21 |
| -fn parse_int<T: std::str::FromStr>( |
22 |
| - matches: &clap::ArgMatches, |
23 |
| - arg_name: &str, |
24 |
| - default: Option<T>, |
25 |
| -) -> JoshResult<T> |
26 |
| -where |
27 |
| - <T as std::str::FromStr>::Err: std::fmt::Display, |
28 |
| -{ |
29 |
| - let arg = matches.get_one::<String>(arg_name).map(|s| s.as_str()); |
30 |
| - |
31 |
| - let arg = match (arg, default) { |
32 |
| - (None, None) => { |
33 |
| - return Err(josh_error(&format!( |
34 |
| - "missing required argument: {}", |
35 |
| - arg_name |
36 |
| - ))) |
37 |
| - } |
38 |
| - (None, Some(default)) => Ok(default), |
39 |
| - (Some(value), _) => value.parse::<T>(), |
40 |
| - }; |
41 |
| - |
42 |
| - arg.map_err(|e| josh_error(&format!("error parsing argument {}: {}", arg_name, e))) |
43 |
| -} |
44 |
| - |
45 |
| -fn make_command() -> clap::Command { |
46 |
| - clap::Command::new("josh-proxy") |
47 |
| - .arg( |
48 |
| - clap::Arg::new("remote") |
49 |
| - .long("remote") |
50 |
| - .action(clap::ArgAction::Append), |
51 |
| - ) |
52 |
| - .arg(clap::Arg::new("local").long("local")) |
53 |
| - .arg(clap::Arg::new("poll").long("poll")) |
54 |
| - .arg( |
55 |
| - clap::Arg::new("gc") |
56 |
| - .long("gc") |
57 |
| - .action(clap::ArgAction::SetTrue) |
58 |
| - .help("Run git gc during maintenance"), |
59 |
| - ) |
60 |
| - .arg( |
61 |
| - clap::Arg::new("require-auth") |
62 |
| - .long("require-auth") |
63 |
| - .action(clap::ArgAction::SetTrue), |
64 |
| - ) |
65 |
| - .arg( |
66 |
| - clap::Arg::new("no-background") |
67 |
| - .long("no-background") |
68 |
| - .action(clap::ArgAction::SetTrue), |
69 |
| - ) |
70 |
| - .arg(clap::Arg::new("n").short('n').help( |
71 |
| - "DEPRECATED - no effect! Number of concurrent upstream git fetch/push operations", |
72 |
| - )) |
73 |
| - .arg(clap::Arg::new("port").long("port")) |
74 |
| - .arg( |
75 |
| - clap::Arg::new("cache-duration") |
76 |
| - .long("cache-duration") |
77 |
| - .short('c') |
78 |
| - .help("Duration between forced cache refresh"), |
79 |
| - ) |
80 |
| - .arg( |
81 |
| - clap::Arg::new("static-resource-proxy-target") |
82 |
| - .long("static-resource-proxy-target") |
83 |
| - .help("Proxy static resource requests to a different URL"), |
84 |
| - ) |
85 |
| - .arg( |
86 |
| - clap::Arg::new("filter-prefix") |
87 |
| - .long("filter-prefix") |
88 |
| - .help("Filter to be prefixed to all queries of this instance"), |
89 |
| - ) |
90 |
| -} |
91 |
| - |
92 |
| -fn parse_remotes(values: &[String]) -> JoshResult<Remote> { |
93 |
| - let mut result = Remote { |
94 |
| - http: None, |
95 |
| - ssh: None, |
96 |
| - }; |
97 |
| - |
98 |
| - for value in values { |
99 |
| - match value { |
100 |
| - v if v.starts_with("http://") || v.starts_with("https://") => { |
101 |
| - result.http = match result.http { |
102 |
| - None => Some(v.clone()), |
103 |
| - Some(v) => return Err(josh_error(&format!("HTTP remote already set: {}", v))), |
104 |
| - }; |
105 |
| - } |
106 |
| - v if v.starts_with("ssh://") => { |
107 |
| - result.ssh = match result.ssh { |
108 |
| - None => Some(v.clone()), |
109 |
| - Some(v) => return Err(josh_error(&format!("SSH remote already set: {}", v))), |
110 |
| - }; |
111 |
| - } |
112 |
| - _ => { |
113 |
| - return Err(josh_error(&format!( |
114 |
| - "Unsupported remote protocol: {}", |
115 |
| - value |
116 |
| - ))) |
117 |
| - } |
118 |
| - } |
119 |
| - } |
120 |
| - |
121 |
| - Ok(result) |
122 |
| -} |
123 |
| - |
124 |
| -pub fn parse_args() -> josh::JoshResult<Args> { |
125 |
| - let args = make_command().get_matches_from(std::env::args()); |
126 |
| - |
127 |
| - let remote = args |
128 |
| - .get_many::<String>("remote") |
129 |
| - .ok_or(josh_error("no remote specified"))? |
130 |
| - .cloned() |
131 |
| - .collect::<Vec<_>>(); |
132 |
| - let remote = parse_remotes(&remote)?; |
133 |
| - |
134 |
| - let local = args |
135 |
| - .get_one::<String>("local") |
136 |
| - .ok_or(josh_error("missing local directory"))? |
137 |
| - .clone(); |
138 |
| - |
139 |
| - let poll_user = args.get_one::<String>("poll").map(String::clone); |
140 |
| - let port = parse_int::<u16>(&args, "port", Some(8000))?; |
141 |
| - let cache_duration = parse_int::<u64>(&args, "cache-duration", Some(0))?; |
142 |
| - let static_resource_proxy_target = args |
143 |
| - .get_one::<String>("static-resource-proxy-target") |
144 |
| - .map(String::clone); |
145 |
| - |
146 |
| - let filter_prefix = args.get_one::<String>("filter-prefix").map(String::clone); |
147 |
| - |
148 |
| - Ok(Args { |
149 |
| - remote, |
150 |
| - local, |
151 |
| - poll_user, |
152 |
| - gc: args.get_flag("gc"), |
153 |
| - require_auth: args.get_flag("require-auth"), |
154 |
| - no_background: args.get_flag("no-background"), |
155 |
| - port, |
156 |
| - cache_duration, |
157 |
| - static_resource_proxy_target, |
158 |
| - filter_prefix, |
159 |
| - }) |
160 |
| -} |
161 |
| - |
162 |
| -pub fn parse_args_or_exit(code: i32) -> Args { |
163 |
| - match parse_args() { |
164 |
| - Err(e) => { |
165 |
| - eprintln!("Argument parsing error: {}", e.0); |
166 |
| - std::process::exit(code); |
167 |
| - } |
168 |
| - Ok(args) => args, |
169 |
| - } |
170 |
| -} |
171 |
| - |
172 |
| -#[test] |
173 |
| -fn verify_app() { |
174 |
| - make_command().debug_assert(); |
175 |
| -} |
0 commit comments