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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//! Command line interface definition.
use clap::{Parser, Subcommand};
use crate::engine::providers::ProviderKind;
/// A beautiful network speed test for your terminal.
#[derive(Debug, Parser)]
#[command(name = "netspd", version, about)]
pub struct Cli {
/// Optional mode; without one, netspd runs a speed test.
#[command(subcommand)]
pub command: Option<Commands>,
/// Test against one specific server URL (a `netspd serve` instance
/// or any LibreSpeed-compatible backend)
#[arg(long, value_name = "URL", env = "NETSPD_URL")]
pub url: Option<String>,
/// Speed test provider: librespeed, ookla, fast or custom
#[arg(long, short = 'p', value_name = "NAME", env = "NETSPD_PROVIDER")]
pub provider: Option<ProviderKind>,
/// Run headless: progress on stderr, summary on stdout
#[arg(long)]
pub no_tui: bool,
/// Run headless and print the report as one JSON object on stdout
#[arg(long, conflicts_with = "csv", env = "NETSPD_JSON")]
pub json: bool,
/// Run headless and print the report as CSV on stdout
/// (header + one row per run)
#[arg(long)]
pub csv: bool,
/// Pick a server whose name or host contains this text
/// (case-insensitive)
#[arg(long, short = 's', value_name = "TEXT", env = "NETSPD_SERVER")]
pub server: Option<String>,
/// List reachable servers (nearest first) and exit
#[arg(long)]
pub list_servers: bool,
/// List available providers with descriptions and exit
#[arg(long)]
pub list_providers: bool,
/// Override the duration of each transfer phase, in seconds (3-60)
#[arg(long, short = 'd', value_name = "SECS")]
pub duration: Option<u64>,
/// Override the number of parallel connections (1-16)
#[arg(long, short = 'c', value_name = "N")]
pub connections: Option<usize>,
/// Repeat the test on an interval, e.g. 45s, 10m, 2h (min 30s).
/// Headless mode loops forever; the TUI auto-restarts after results
#[arg(long, short = 'i', value_name = "DURATION", env = "NETSPD_INTERVAL")]
pub interval: Option<String>,
/// Plain ASCII UI: no braille, block art or unicode symbols
#[arg(long)]
pub ascii: bool,
/// Test the N nearest servers and print a ranked comparison
#[arg(long, value_name = "N", conflicts_with_all = ["server", "interval"])]
pub compare: Option<usize>,
/// Print stored results and exit (respects --json / --csv)
#[arg(long)]
pub history: bool,
/// Force IPv4 for measurements
#[arg(short = '4', long = "ipv4", conflicts_with = "ipv6")]
pub ipv4: bool,
/// Force IPv6 for measurements
#[arg(short = '6', long = "ipv6")]
pub ipv6: bool,
/// Exit with code 2 when download falls below this many Mbps
/// (for CI checks and alerting)
#[arg(long, value_name = "MBPS", env = "NETSPD_FAIL_BELOW")]
pub fail_below: Option<f64>,
/// Write Prometheus metrics to this node_exporter textfile after
/// each completed run
#[arg(long, value_name = "PATH", env = "NETSPD_PROM_TEXTFILE")]
pub prom_textfile: Option<std::path::PathBuf>,
/// Print a single compact line on stdout and exit — ideal for tmux
/// status bars, i3bar, waybar, and shell prompts.
/// Output: `↓93.7 ↑66.1 ~12ms A+`
#[arg(long, conflicts_with_all = ["json", "csv", "no_tui"])]
pub one_line: bool,
}
/// Alternate modes.
#[derive(Debug, Subcommand)]
pub enum Commands {
/// Run the built-in speed test server (the target for `--url`),
/// e.g. inside a pod so other pods can measure the link to it
Serve {
/// Port to listen on
#[arg(long, default_value_t = crate::app::serve::DEFAULT_PORT, env = "NETSPD_PORT")]
port: u16,
/// Address to bind
#[arg(long, default_value = "0.0.0.0", env = "NETSPD_BIND")]
bind: String,
},
/// Print shell completions to stdout
Completions {
/// Target shell
shell: clap_complete::Shell,
},
/// Print the man page (roff) to stdout
Man,
}
/// Prints completions for `shell`.
pub fn print_completions(shell: clap_complete::Shell) {
use clap::CommandFactory;
let mut command = Cli::command();
clap_complete::generate(shell, &mut command, "netspd", &mut std::io::stdout());
}
/// Prints the roff man page.
pub fn print_man() -> std::io::Result<()> {
use clap::CommandFactory;
let man = clap_mangen::Man::new(Cli::command());
man.render(&mut std::io::stdout())
}
impl Cli {
/// Whether any flag requests a run without the TUI.
#[must_use]
pub fn headless(&self) -> bool {
self.no_tui
|| self.json
|| self.csv
|| self.one_line
|| self.list_servers
|| self.list_providers
|| self.compare.is_some()
|| self.history
}
}