Skip to main content

cargo/
cargo.rs

1//! A very partial unfaithful implementation of cargo's command line.
2//!
3//! This showcases some hairier patterns, like subcommands and custom value parsing.
4
5use std::{path::PathBuf, str::FromStr};
6
7const HELP: &str = "cargo [+toolchain] [OPTIONS] [SUBCOMMAND]";
8
9fn main() -> Result<(), lexopt::Error> {
10    use lexopt::prelude::*;
11
12    let mut settings = GlobalSettings {
13        toolchain: "stable".to_owned(),
14        color: Color::Auto,
15        offline: false,
16        quiet: false,
17        verbose: false,
18    };
19
20    let mut parser = lexopt::Parser::from_env();
21    while let Some(arg) = parser.next()? {
22        match arg {
23            Long("color") => {
24                settings.color = parser.value()?.parse()?;
25            }
26            Long("offline") => {
27                settings.offline = true;
28            }
29            Long("quiet") => {
30                settings.quiet = true;
31                settings.verbose = false;
32            }
33            Long("verbose") => {
34                settings.verbose = true;
35                settings.quiet = false;
36            }
37            Long("help") => {
38                println!("{}", HELP);
39                std::process::exit(0);
40            }
41            Value(value) => {
42                let value = value.string()?;
43                match value.as_str() {
44                    value if value.starts_with('+') => {
45                        settings.toolchain = value[1..].to_owned();
46                    }
47                    "install" => {
48                        return install(settings, parser);
49                    }
50                    value => {
51                        return Err(format!("unknown subcommand '{}'", value).into());
52                    }
53                }
54            }
55            _ => return Err(arg.unexpected()),
56        }
57    }
58
59    println!("{}", HELP);
60    Ok(())
61}
62
63#[derive(Debug)]
64struct GlobalSettings {
65    toolchain: String,
66    color: Color,
67    offline: bool,
68    quiet: bool,
69    verbose: bool,
70}
71
72fn install(settings: GlobalSettings, mut parser: lexopt::Parser) -> Result<(), lexopt::Error> {
73    use lexopt::prelude::*;
74
75    let mut package: Option<String> = None;
76    let mut root: Option<PathBuf> = None;
77    let mut jobs: u16 = get_no_of_cpus();
78
79    while let Some(arg) = parser.next()? {
80        match arg {
81            Value(value) if package.is_none() => {
82                package = Some(value.string()?);
83            }
84            Long("root") => {
85                root = Some(parser.value()?.into());
86            }
87            Short('j') | Long("jobs") => {
88                jobs = parser.value()?.parse()?;
89            }
90            Long("help") => {
91                println!("cargo install [OPTIONS] CRATE");
92                std::process::exit(0);
93            }
94            _ => return Err(arg.unexpected()),
95        }
96    }
97
98    println!("Settings: {:#?}", settings);
99    println!(
100        "Installing {} into {:?} with {} jobs",
101        package.ok_or("missing CRATE argument")?,
102        root,
103        jobs
104    );
105
106    Ok(())
107}
108
109#[derive(Debug)]
110enum Color {
111    Auto,
112    Always,
113    Never,
114}
115
116// clap has a macro for this: https://docs.rs/clap/2.33.3/clap/macro.arg_enum.html
117// We have to do it manually.
118impl FromStr for Color {
119    type Err = String;
120
121    fn from_str(s: &str) -> Result<Self, Self::Err> {
122        match s.to_lowercase().as_str() {
123            "auto" => Ok(Color::Auto),
124            "always" => Ok(Color::Always),
125            "never" => Ok(Color::Never),
126            _ => Err(format!(
127                "Invalid style '{}' [pick from: auto, always, never]",
128                s
129            )),
130        }
131    }
132}
133
134fn get_no_of_cpus() -> u16 {
135    4
136}