1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
use std::ffi::OsString;
use clap::{Args, Parser, Subcommand, ValueEnum};
/// A Rust Tor relay implementation.
#[derive(Clone, Debug, Parser)]
#[command(author = "The Tor Project Developers")]
#[command(version)]
pub(crate) struct Cli {
#[command(subcommand)]
pub(crate) command: Commands,
/// Override the log level from the configuration.
#[arg(long, short, global = true)]
#[arg(value_name = "LEVEL")]
#[clap(default_value_t = LogLevel::Info)]
pub(crate) log_level: LogLevel,
/// Don't check permissions on the files we use.
#[arg(long, global = true)]
pub(crate) disable_fs_permission_checks: bool,
/// Override config file parameters, using TOML-like syntax.
#[arg(long = "option", short, global = true)]
#[arg(value_name = "KEY=VALUE")]
pub(crate) options: Vec<String>,
/// Config file(s) to read.
#[arg(long, short, global = true)]
#[arg(value_name = "FILE")]
#[clap(default_values_t = default_config_files().into_iter().map(CliOsString))]
pub(crate) config: Vec<CliOsString>,
}
/// Main subcommands.
#[derive(Clone, Debug, Subcommand)]
pub(crate) enum Commands {
/// Run the relay.
Run(RunArgs),
/// Print build information.
BuildInfo,
}
/// Arguments when running an Arti relay.
#[derive(Clone, Debug, Args)]
pub(crate) struct RunArgs {}
/// Paths used for default configuration files.
fn default_config_files() -> Vec<OsString> {
// TODO: these are temporary default paths
vec![
"~/.config/arti-relay/arti-relay.toml".into(),
"~/.config/arti-relay/arti-relay.d/".into(),
]
}
/// Log levels allowed by the cli.
#[derive(Clone, Debug, Eq, PartialEq, ValueEnum)]
pub(crate) enum LogLevel {
Error,
Warn,
Info,
Debug,
Trace,
}
impl std::fmt::Display for LogLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Error => write!(f, "error"),
Self::Warn => write!(f, "warn"),
Self::Info => write!(f, "info"),
Self::Debug => write!(f, "debug"),
Self::Trace => write!(f, "trace"),
}
}
}
/// An [`OsString`] wrapper which implements `Display`; designed for use with the cli help text.
#[derive(Debug, Clone, Eq, PartialEq, derive_more::From)]
pub(crate) struct CliOsString(pub(crate) OsString);
impl std::fmt::Display for CliOsString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// we can't (and don't want to) write non-utf-8 bytes in the cli help output
self.0.to_string_lossy().fmt(f)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn common_flags() {
Cli::parse_from(["arti-relay", "build-info"]);
Cli::parse_from(["arti-relay", "run"]);
let cli = Cli::parse_from(["arti-relay", "--log-level", "warn", "run"]);
assert_eq!(cli.log_level, LogLevel::Warn);
let cli = Cli::parse_from(["arti-relay", "run", "--log-level", "warn"]);
assert_eq!(cli.log_level, LogLevel::Warn);
let cli = Cli::parse_from(["arti-relay", "--disable-fs-permission-checks", "run"]);
assert!(cli.disable_fs_permission_checks);
let cli = Cli::parse_from(["arti-relay", "run", "--disable-fs-permission-checks"]);
assert!(cli.disable_fs_permission_checks);
}
#[test]
fn clap_bug() {
let cli = Cli::parse_from(["arti-relay", "-o", "foo=1", "run"]);
assert_eq!(cli.options, vec!["foo=1"]);
let cli = Cli::parse_from(["arti-relay", "-o", "foo=1", "-o", "bar=2", "run"]);
assert_eq!(cli.options, vec!["foo=1", "bar=2"]);
// this is https://github.com/clap-rs/clap/issues/3938
// TODO: this is a footgun, and we should consider alternatives to clap's 'global' args
let cli = Cli::parse_from(["arti-relay", "-o", "foo=1", "run", "-o", "bar=2"]);
assert_eq!(cli.options, vec!["bar=2"]);
}
}
|