1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-27 19:17:43 +00:00

feat(unexpand): move from getopts to clap (#1883)

* feat: move unexpand to clap

* chore: allow muliple files

* test: add test fixture, test reading from a file

* test: fix typo on file name, add test for multiple inputs

* chore: use 'success()' instead of asserting

* chore: delete unused variables

* chore: use help instead of long_help, break long line
This commit is contained in:
Yagiz Degirmenci 2021-03-23 11:42:05 +03:00 committed by GitHub
parent 5e2e2e8ab6
commit 545fe7d887
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 76 additions and 66 deletions

View file

@ -15,7 +15,7 @@ edition = "2018"
path = "src/unexpand.rs" path = "src/unexpand.rs"
[dependencies] [dependencies]
getopts = "0.2.18" clap = "2.33"
unicode-width = "0.1.5" unicode-width = "0.1.5"
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" } uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -11,7 +11,7 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use clap::{App, Arg};
use std::fs::File; use std::fs::File;
use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Stdout, Write}; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Stdout, Write};
use std::str::from_utf8; use std::str::from_utf8;
@ -19,6 +19,9 @@ use unicode_width::UnicodeWidthChar;
static NAME: &str = "unexpand"; static NAME: &str = "unexpand";
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static USAGE: &str = "unexpand [OPTION]... [FILE]...";
static SUMMARY: &str = "Convert blanks in each FILE to tabs, writing to standard output.\n
With no FILE, or when FILE is -, read standard input.";
const DEFAULT_TABSTOP: usize = 8; const DEFAULT_TABSTOP: usize = 8;
@ -46,6 +49,14 @@ fn tabstops_parse(s: String) -> Vec<usize> {
nums nums
} }
mod options {
pub const FILE: &str = "file";
pub const ALL: &str = "all";
pub const FIRST_ONLY: &str = "first-only";
pub const TABS: &str = "tabs";
pub const NO_UTF8: &str = "no-utf8";
}
struct Options { struct Options {
files: Vec<String>, files: Vec<String>,
tabstops: Vec<usize>, tabstops: Vec<usize>,
@ -54,20 +65,19 @@ struct Options {
} }
impl Options { impl Options {
fn new(matches: getopts::Matches) -> Options { fn new(matches: clap::ArgMatches) -> Options {
let tabstops = match matches.opt_str("t") { let tabstops = match matches.value_of(options::TABS) {
None => vec![DEFAULT_TABSTOP], None => vec![DEFAULT_TABSTOP],
Some(s) => tabstops_parse(s), Some(s) => tabstops_parse(s.to_string()),
}; };
let aflag = (matches.opt_present("all") || matches.opt_present("tabs")) let aflag = (matches.is_present(options::ALL) || matches.is_present(options::TABS))
&& !matches.opt_present("first-only"); && !matches.is_present(options::FIRST_ONLY);
let uflag = !matches.opt_present("U"); let uflag = !matches.is_present(options::NO_UTF8);
let files = if matches.free.is_empty() { let files = match matches.value_of(options::FILE) {
vec!["-".to_owned()] Some(v) => vec![v.to_string()],
} else { None => vec!["-".to_owned()],
matches.free
}; };
Options { Options {
@ -82,60 +92,39 @@ impl Options {
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args.collect_str();
let mut opts = getopts::Options::new(); let matches = App::new(executable!())
.name(NAME)
opts.optflag( .version(VERSION)
"a", .usage(USAGE)
"all", .about(SUMMARY)
"convert all blanks, instead of just initial blanks", .arg(Arg::with_name(options::FILE).hidden(true).multiple(true))
); .arg(
opts.optflag( Arg::with_name(options::ALL)
"", .short("a")
"first-only", .long(options::ALL)
"convert only leading sequences of blanks (overrides -a)", .help("convert all blanks, instead of just initial blanks")
); .takes_value(false),
opts.optopt( )
"t", .arg(
"tabs", Arg::with_name(options::FIRST_ONLY)
"have tabs N characters apart instead of 8 (enables -a)", .long(options::FIRST_ONLY)
"N", .help("convert only leading sequences of blanks (overrides -a)")
); .takes_value(false),
opts.optopt( )
"t", .arg(
"tabs", Arg::with_name(options::TABS)
"use comma separated LIST of tab positions (enables -a)", .short("t")
"LIST", .long(options::TABS)
); .long_help("use comma separated LIST of tab positions or have tabs N characters apart instead of 8 (enables -a)")
opts.optflag( .takes_value(true)
"U", )
"no-utf8", .arg(
"interpret input file as 8-bit ASCII rather than UTF-8", Arg::with_name(options::NO_UTF8)
); .short("U")
opts.optflag("h", "help", "display this help and exit"); .long(options::NO_UTF8)
opts.optflag("V", "version", "output version information and exit"); .takes_value(false)
.help("interpret input file as 8-bit ASCII rather than UTF-8"))
let matches = match opts.parse(&args[1..]) { .get_matches_from(args);
Ok(m) => m,
Err(f) => crash!(1, "{}", f),
};
if matches.opt_present("help") {
println!("{} {}\n", NAME, VERSION);
println!("Usage: {} [OPTION]... [FILE]...\n", NAME);
println!(
"{}",
opts.usage(
"Convert blanks in each FILE to tabs, writing to standard output.\n\
With no FILE, or when FILE is -, read standard input."
)
);
return 0;
}
if matches.opt_present("V") {
println!("{} {}", NAME, VERSION);
return 0;
}
unexpand(Options::new(matches)); unexpand(Options::new(matches));

View file

@ -136,3 +136,22 @@ fn unexpand_spaces_after_fields() {
.run() .run()
.stdout_is("\t\tA B C D\t\t A\t\n"); .stdout_is("\t\tA B C D\t\t A\t\n");
} }
#[test]
fn unexpand_read_from_file() {
new_ucmd!()
.arg("with_spaces.txt")
.arg("-t4")
.run()
.success();
}
#[test]
fn unexpand_read_from_two_file() {
new_ucmd!()
.arg("with_spaces.txt")
.arg("with_spaces.txt")
.arg("-t4")
.run()
.success();
}

View file

@ -0,0 +1,2 @@
abc d e f g \t\t A