Skip to main content

subcommands/
subcommands.rs

1//! Example: subcommands — demonstrates #[derive(CliCommand)]
2//!
3//! mytool download <url>
4//! mytool upload <file> <remote>
5//! mytool net download <url>
6//! mytool net ping
7//! mytool status
8
9use cli_ui::progress::format_bytes;
10use cli_ui::styles::{paint, CYAN, DIM, OK, YELLOW};
11use cli_ui::{header, ok, phase, step, summary, CliCommand, CliOptions, Result};
12use std::path::PathBuf;
13
14// ── Global options ────────────────────────────────────────────────────────────
15
16#[derive(CliOptions)]
17struct Global {
18    /// Enable verbose output
19    #[arg(short = 'v', long = "verbose", negatable)]
20    verbose: bool,
21
22    /// Path to config file
23    #[arg(short = 'c', long = "config")]
24    config: Option<PathBuf>,
25}
26
27// ── Command options ───────────────────────────────────────────────────────────
28
29#[derive(CliOptions)]
30#[cli(
31    about = "download a file from URL",
32    example = "mytool download https://example.com/file.csv",
33    example = "mytool dl https://example.com/file.csv -o ./data/ -j 8"
34)]
35struct DownloadOpt {
36    /// URL to download
37    #[arg(positional)]
38    url: String,
39
40    /// Output directory
41    #[arg(short = 'o', long = "output", default = ".")]
42    output: PathBuf,
43
44    /// Parallel connections
45    #[arg(section = "Performance", short = 'j', long = "jobs", default = 4)]
46    jobs: usize,
47}
48
49#[derive(CliOptions)]
50#[cli(about = "upload a file to remote storage")]
51struct UploadOpt {
52    /// Local file path
53    #[arg(positional)]
54    file: PathBuf,
55
56    /// Remote destination
57    #[arg(positional)]
58    remote: String,
59
60    /// Compress before upload
61    #[arg(section = "Transfer", short = 'z', long = "compress", negatable)]
62    compress: bool,
63}
64
65#[derive(CliOptions)]
66#[cli(
67    about = "download from a network endpoint",
68    example = "mytool net download https://example.com/data --timeout 60"
69)]
70struct NetDownloadOpt {
71    /// Target URL
72    #[arg(positional)]
73    url: String,
74
75    /// Timeout in seconds
76    #[arg(long = "timeout", default = 30)]
77    timeout: u32,
78}
79
80// ── Nested subcommands ────────────────────────────────────────────────────────
81
82#[derive(CliCommand)]
83#[cli(about = "network diagnostics and operations")]
84enum NetCmd {
85    /// Download from a network endpoint
86    #[cli(alias = "dl")]
87    Download(NetDownloadOpt), // mytool net download <url>
88    // mytool net dl <url>
89    /// Check network connectivity
90    Ping, // mytool net ping
91}
92
93// ── Root subcommands ──────────────────────────────────────────────────────────
94
95#[derive(CliCommand)]
96#[cli(
97    name    = "mytool",
98    about   = "batch file processor",
99    tagline = "fast and parallel",
100    theme   = "cyan",
101    global  = Global,
102    example = "mytool download https://example.com/file.csv",
103    example = "mytool -v upload ./report.pdf s3://bucket/report.pdf --compress",
104    url     = "https://github.com/you/mytool",
105)]
106enum Cmd {
107    /// Download a file from URL
108    #[cli(alias = "dl")]
109    Download(DownloadOpt), // mytool download <url> | mytool dl <url>
110
111    /// Upload a file to remote storage
112    #[cli(alias = "up")]
113    Upload(UploadOpt), // mytool upload <file> <remote> | mytool up ...
114
115    /// Network diagnostics
116    #[cli(alias = "net")]
117    Network(NetCmd), // mytool network ... | mytool net ...
118
119    /// Show current status
120    Status, // mytool status — unit variant, no options
121}
122
123// ── Handlers ──────────────────────────────────────────────────────────────────
124
125fn download(g: &Global, opt: DownloadOpt) {
126    header!(
127        "mytool",
128        env!("CARGO_PKG_VERSION"),
129        "batch file processor",
130        "fast and parallel"
131    );
132    phase!("download", "{}", opt.url);
133    if g.verbose {
134        phase!("debug", "output={} jobs={}", opt.output.display(), opt.jobs);
135    }
136    step!("fetching");
137    ok!(opt.output.join("file.csv").display());
138    summary! {
139        done:   "Download complete",
140        "url"    => paint(CYAN, &opt.url),
141        "output" => paint(CYAN, &opt.output.display().to_string()),
142        section,
143        "size"   => paint(YELLOW, &format_bytes(43_100)),
144        "time"   => paint(DIM, "320ms"),
145    }
146}
147
148fn upload(g: &Global, opt: UploadOpt) {
149    header!(
150        "mytool",
151        env!("CARGO_PKG_VERSION"),
152        "batch file processor",
153        "fast and parallel"
154    );
155    phase!("upload", "{} → {}", opt.file.display(), opt.remote);
156    if g.verbose {
157        phase!("debug", "compress={}", opt.compress);
158    }
159    step!("uploading");
160    ok!(opt.remote.as_str());
161    summary! {
162        done:   "Upload complete",
163        "file"   => paint(CYAN, &opt.file.display().to_string()),
164        "remote" => paint(CYAN, &opt.remote),
165    }
166}
167
168fn net_download(g: &Global, opt: NetDownloadOpt) {
169    header!(
170        "mytool",
171        env!("CARGO_PKG_VERSION"),
172        "batch file processor",
173        "fast and parallel"
174    );
175    phase!("net-download", "{} (timeout: {}s)", opt.url, opt.timeout);
176    if g.verbose {
177        phase!("debug", "using network stack");
178    }
179    step!("fetching");
180    ok!("./data/file.bin");
181    summary! {
182        done:  "Download complete",
183        "url"  => paint(CYAN, &opt.url),
184        "time" => paint(DIM, "120ms"),
185    }
186}
187
188fn net_ping(g: &Global) {
189    header!(
190        "mytool",
191        env!("CARGO_PKG_VERSION"),
192        "batch file processor",
193        "fast and parallel"
194    );
195    phase!("ping", "checking connectivity...");
196    if g.verbose {
197        phase!("debug", "sending ICMP");
198    }
199    summary! {
200        done:      "Reachable",
201        "latency"  => paint(OK, "12ms"),
202    }
203}
204
205fn status(g: &Global) {
206    header!(
207        "mytool",
208        env!("CARGO_PKG_VERSION"),
209        "batch file processor",
210        "fast and parallel"
211    );
212    phase!("status", "checking...");
213    if g.verbose {
214        phase!("debug", "reading config");
215    }
216    summary! {
217        done:      "All systems operational",
218        "version"  => paint(OK, env!("CARGO_PKG_VERSION")),
219        "config"   => paint(DIM, &g.config.as_deref()
220                        .map(|p| p.display().to_string())
221                        .unwrap_or_else(|| "default".into())),
222    }
223}
224
225// ── main ─────────────────────────────────────────────────────────────────────
226
227fn main() -> Result<()> {
228    match Cmd::parse()? {
229        Cmd::Download(opt) => download(Cmd::global(), opt),
230        Cmd::Upload(opt) => upload(Cmd::global(), opt),
231        Cmd::Status => status(Cmd::global()),
232
233        Cmd::Network(sub) => match sub {
234            NetCmd::Download(opt) => net_download(Cmd::global(), opt),
235            NetCmd::Ping => net_ping(Cmd::global()),
236        },
237    }
238
239    Ok(())
240}