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:
parent
5e2e2e8ab6
commit
545fe7d887
4 changed files with 76 additions and 66 deletions
|
@ -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" }
|
||||||
|
|
|
@ -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));
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
2
tests/fixtures/unexpand/with_spaces.txt
vendored
Normal file
2
tests/fixtures/unexpand/with_spaces.txt
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
abc d e f g \t\t A
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue