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);
}
}