Skip to content

Command line

Không sử dụng crate ngoài


pub struct Config {
    pub query: String,
    pub filename: String,
    pub case_sensitive: bool,
}

impl Config {
    pub fn new(mut args: env::Args) -> Result<Config, &'static str> {
        args.next();

        let query = match args.next() {
            Some(arg) => arg,
            None => return Err("Didn't get a query string"),
        };

        let filename = match args.next() {
            Some(arg) => arg,
            None => return Err("Didn't get a file name"),
        };

        let case_sensitive = env::var("CASE_INSENSITIVE").is_err();

        Ok(Config { query, filename, case_sensitive })
    }
}

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let contents = fs::read_to_string(config.filename)?;

    let results = if config.case_sensitive {
        search(&config.query, &contents)
    } else {
        search_case_insensitive(&config.query, &contents)
    };

    for line in results {
        println!("{}", line);
    }

    Ok(())
}

Parsing arguments sử dụng clap

Sử dụng cơ bản

This application describes the structure of its command-line interface using clap's builder style. The documentation gives two other possible ways to instantiate an application.

In the builder style, with_name is the unique identifier that value_of will use to retrieve the value passed. The short and long options control the flag the user will be expected to type; short flags look like -f and long flags look like --file.

use clap::{Arg, App};

fn main() {
    let matches = App::new("My Test Program")
        .version("0.1.0")
        .author("Hackerman Jones <hckrmnjones@hack.gov>")
        .about("Teaches argument parsing")
        .arg(Arg::with_name("file")
                 .short("f")
                 .long("file")
                 .takes_value(true)
                 .help("A cool file"))
        .arg(Arg::with_name("num")
                 .short("n")
                 .long("number")
                 .takes_value(true)
                 .help("Five less than your favorite number"))
        .get_matches();

    let myfile = matches.value_of("file").unwrap_or("input.txt");
    println!("The file passed is: {}", myfile);

    let num_str = matches.value_of("num");
    match num_str {
        None => println!("No idea what your favorite number is."),
        Some(s) => {
            match s.parse::<i32>() {
                Ok(n) => println!("Your favorite number must be {}.", n + 5),
                Err(_) => println!("That's not a number! {}", s),
            }
        }
    }
}

Usage information is generated by clap. The usage for the example application looks like this.

My Test Program 0.1.0
Hackerman Jones <hckrmnjones@hack.gov>
Teaches argument parsing

USAGE:
    testing [OPTIONS]

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

OPTIONS:
    -f, --file <file>     A cool file
    -n, --number <num>    Five less than your favorite number

We can test the application by running a command like the following.

$ cargo run -- -f myfile.txt -n 251

The output is:

The file passed is: myfile.txt
Your favorite number must be 256.

Nâng cao

use clap::Parser;
use std::path::PathBuf;

#[derive(Parser, Debug, Clone)]
#[clap(
    author,
    version,
    about,
    long_about = "Note: When playing, all the keybindings of mpv can be used, and `q` is reserved for exiting the program"
)]
pub struct Cli {
    /// Option: -u --url <URL>: Specifies an url to be played.
    #[clap(short, long, help = "Specifies an url to be played.")]
    pub url: Option<String>,

    /// Option: -s --station <station name>: Specifies the name of the station to be played
    #[clap(
        short,
        long,
        conflicts_with = "url",
        help = "Specifies the name of the station to be played."
    )]
    pub station: Option<String>,

    /// Flag: --show-video: If *not* present, a flag is passed down to mpv to not show the video and just play the audio.
    #[clap(
        long = "show-video",
        help = "If *not* present, a flag is passed down to mpv to not show the video and just play the audio."
    )]
    pub show_video: bool,

    /// Option: -c --config: Specify a config file other than the default.
    #[clap(
        long,
        short,
        help = "Specify a different config file from the default one."
    )]
    pub config: Option<PathBuf>,

    /// Option: --country-code <CODE>: Specify a country code to filter the search results
    #[clap(
        long = "country-code",
        help = "Specify a country code to filter the search."
    )]
    pub country_code: Option<String>,

    /// Flag: --list-countries: List all the available countries and country codes to put in the config.
    #[clap(
        long = "list-countries",
        help = "List all the available countries and country codes to put in the config."
    )]
    pub list_countries: bool,

    /// Flag: --no-station-cache: Don't cache the station list loaded from the internet.
    #[clap(
        long = "no-station-cache",
        help = "Don't cache the station list loaded from the internet."
    )]
    pub no_station_cache: bool,

    /// Show extra info
    #[clap(flatten)]
    pub verbose: clap_verbosity_flag::Verbosity,

    /// Show debug info
    #[structopt(short, long)]
    pub debug: bool,
}

Một só cái khác chưa sắp xếp

Đọc input từ người dùng

use std::fs::File;
use std::io::{self, Read};

pub fn read_from_stdin() -> String {
    let mut input = String::new();
    loop {
        match io::stdin().read_line(&mut input) {
            Ok(len) => if len == 0 {
                break;
            }
            Err(error) => {
                eprintln!("error: {}", error);
                break;
            }
        }
    }
    input
}

Đây mới là cái Reader

use std::io;

pub trait ReadInput {
    fn read_input(&mut self) -> io::Result<String>;
}

pub struct Reader<R> {
    reader: R,
}

impl<R> Reader<R> {
    pub fn new(reader: R) -> Self {
        Self { reader }
    }
}

impl<R: io::BufRead> ReadInput for Reader<R> {
    fn read_input(&mut self) -> io::Result<String> {
        let mut input = String::new();
        self.reader.read_line(&mut input)?;
        Ok(input.trim().to_string())
    }
}

#[allow(non_snake_case)]
#[cfg(test)]
mod tests {
    use crate::reader::{ReadInput, Reader};

    #[test]
    fn test_reader__read_input__success() {
        let input = b"  my input with whitespace chars  ";
        let mut reader = Reader::new(&input[..]);

        let actual = reader.read_input().unwrap();
        let expected = "my input with whitespace chars".to_string();

        assert_eq!(actual, expected);
    }
}

Tất tần tật về Print trong giao diện dòng lệnh

use std::io;
use std::io::Write;

pub trait Print {
    fn print(&mut self, value: &str) -> io::Result<()>;
    fn println(&mut self, value: &str) -> io::Result<()>;
}

pub trait PrintColor {
    fn fts_banner(&mut self) -> io::Result<()>;
    fn input_header(&mut self, value: &str) -> io::Result<()>;
    fn error(&mut self, value: &str) -> io::Result<()>;
}

pub struct Printer<W> {
    writer: W,
}

#[derive(Clone, Copy)]
pub struct PrintOptions {
    color: termcolor::Color,
    is_bold: bool,
}

impl<W: Write + termcolor::WriteColor> Printer<W> {
    pub fn new(writer: W) -> Self {
        Self { writer }
    }
}

impl<W: Write> Print for Printer<W> {
    fn print(&mut self, value: &str) -> io::Result<()> {
        write!(self.writer, "{}", value)
    }

    fn println(&mut self, value: &str) -> io::Result<()> {
        writeln!(self.writer, "{}", value)
    }
}

impl<W: Write + termcolor::WriteColor> PrintColor for Printer<W> {
    fn fts_banner(&mut self) -> io::Result<()> {
        let opts = PrintOptions {
            color: termcolor::Color::Yellow,
            is_bold: false,
        };
        let banner = format!(
            "{}\n{}{}{}{}{}\n{}",
            "#".repeat(60),
            "#".repeat(4),
            " ".repeat(18),
            "First Time Setup",
            " ".repeat(18),
            "#".repeat(4),
            "#".repeat(60)
        );
        let description = r#"
This tool requires you to have a repository with a README.md
in the root folder. The markdown file is where your ideas
will be stored.

Once first time setup has completed, simply run Eureka again
to begin writing down ideas.
        "#;
        self.println_styled(&format!("{}\n{}", banner.as_str(), description), opts)
    }

    fn input_header(&mut self, value: &str) -> io::Result<()> {
        let opts = PrintOptions {
            color: termcolor::Color::Green,
            is_bold: true,
        };
        self.println_styled(value, opts)?;
        self.print("> ")?;
        self.writer.flush()
    }

    fn error(&mut self, value: &str) -> io::Result<()> {
        let opts = PrintOptions {
            color: termcolor::Color::Red,
            is_bold: false,
        };
        self.println_styled(value, opts)?;
        self.writer.flush()
    }
}

impl<W: Write + termcolor::WriteColor> Printer<W> {
    fn println_styled(&mut self, value: &str, opts: PrintOptions) -> io::Result<()> {
        let mut color_spec = termcolor::ColorSpec::new();
        color_spec.set_fg(Some(opts.color)).set_bold(opts.is_bold);
        self.writer.set_color(&color_spec)?;
        writeln!(self.writer, "{}", value)?;
        self.writer.reset()
    }
}

#[allow(non_snake_case)]
#[cfg(test)]
mod tests {
    use crate::printer::{Print, PrintColor, PrintOptions, Printer};

    #[test]
    fn test_printer__print__success() {
        let mut output = Vec::new();
        let mut printer = Printer {
            writer: &mut output,
        };

        let print_result = printer.print("this value");
        assert!(print_result.is_ok());

        let actual = String::from_utf8(output).expect("Not UTF-8");
        let expected = "this value";

        assert_eq!(actual, expected);
    }

    #[test]
    fn test_printer__println__success() {
        let mut output = Vec::new();
        let mut printer = Printer {
            writer: &mut output,
        };

        let print_result = printer.println("this value");
        assert!(print_result.is_ok());

        let actual = String::from_utf8(output).expect("Not UTF-8");
        let expected = "this value\n";

        assert_eq!(actual, expected);
    }

    #[test]
    fn test_printer__fts_banner__success() {
        let mut output = termcolor::Ansi::new(vec![]);
        let mut printer = Printer::new(&mut output);

        printer.fts_banner().unwrap();

        let actual = String::from_utf8(output.into_inner()).unwrap();
        let expected = "############################################################
####                  First Time Setup                  ####
############################################################

This tool requires you to have a repository with a README.md
in the root folder. The markdown file is where your ideas
will be stored.

Once first time setup has completed, simply run Eureka again
to begin writing down ideas.";

        assert!(actual.starts_with("\u{1b}[0m\u{1b}[33m"));
        assert!(actual.contains(expected));
        assert!(actual.ends_with("\n\u{1b}[0m"));
    }

    #[test]
    fn test_printer__input_header__success() {
        let mut output = termcolor::Ansi::new(vec![]);
        let mut printer = Printer::new(&mut output);

        printer.input_header("some-value").unwrap();

        let actual = String::from_utf8(output.into_inner()).unwrap();
        let expected = "\u{1b}[0m\u{1b}[1m\u{1b}[32msome-value\n\u{1b}[0m> ";

        assert_eq!(actual, expected);
    }

    #[test]
    fn test_printer__error__success() {
        let mut output = termcolor::Ansi::new(vec![]);
        let mut printer = Printer::new(&mut output);

        printer.error("some-value").unwrap();

        let actual = String::from_utf8(output.into_inner()).unwrap();
        let expected = "\u{1b}[0m\u{1b}[31msome-value\n\u{1b}[0m";

        assert_eq!(actual, expected);
    }

    #[test]
    fn test_printer__println_styled__success() {
        let mut output_1 = termcolor::Ansi::new(vec![]);
        let mut printer = Printer::new(&mut output_1);

        let opts_green_bold = PrintOptions {
            color: termcolor::Color::Green,
            is_bold: true,
        };

        printer
            .println_styled("some-green-bold-text", opts_green_bold)
            .unwrap();

        let actual_green_bold = String::from_utf8(output_1.into_inner()).unwrap();
        let expected_green_bold = "\u{1b}[0m\u{1b}[1m\u{1b}[32msome-green-bold-text\n\u{1b}[0m";

        assert_eq!(actual_green_bold, expected_green_bold);

        let mut output_2 = termcolor::Ansi::new(vec![]);
        printer = Printer::new(&mut output_2);

        let opts_yellow = PrintOptions {
            color: termcolor::Color::Yellow,
            is_bold: false,
        };

        printer
            .println_styled("some-yellow-text", opts_yellow)
            .unwrap();

        let actual_yellow = String::from_utf8(output_2.into_inner()).unwrap();
        let expected_yellow = "\u{1b}[0m\u{1b}[33msome-yellow-text\n\u{1b}[0m";

        assert_eq!(actual_yellow, expected_yellow);
    }
}