base64: Refactor argument parsing

Moved most of the argument parsing logic to `base32/base_common.rs` to
allow for significant code reuse.
This commit is contained in:
Ricardo Iglesias 2021-04-29 01:59:43 -07:00
parent 05b20c32a9
commit f307de22d0
8 changed files with 187 additions and 287 deletions

1
Cargo.lock generated
View file

@ -1689,6 +1689,7 @@ name = "uu_base64"
version = "0.0.6"
dependencies = [
"clap",
"uu_base32",
"uucore",
"uucore_procs",
]

View file

@ -7,19 +7,14 @@
#[macro_use]
extern crate uucore;
use std::io::{stdin, Read};
use uucore::encoding::Format;
use uucore::InvalidEncodingHandling;
use std::fs::File;
use std::io::{stdin, BufReader};
use std::path::Path;
pub mod base_common;
use clap::{App, Arg};
mod base_common;
static SUMMARY: &str = "Base32 encode or decode FILE, or standard input, to standard output.";
static LONG_HELP: &str = "
static ABOUT: &str = "
With no FILE, or when FILE is -, read standard input.
The data are encoded as described for the base32 alphabet in RFC
@ -30,126 +25,41 @@ static LONG_HELP: &str = "
";
static VERSION: &str = env!("CARGO_PKG_VERSION");
static BASE_CMD_PARSE_ERROR: i32 = 1;
fn get_usage() -> String {
format!("{0} [OPTION]... [FILE]", executable!())
}
pub mod options {
pub static DECODE: &str = "decode";
pub static WRAP: &str = "wrap";
pub static IGNORE_GARBAGE: &str = "ignore-garbage";
pub static FILE: &str = "file";
}
struct Config {
decode: bool,
ignore_garbage: bool,
wrap_cols: Option<usize>,
to_read: Option<String>,
}
impl Config {
fn from(options: clap::ArgMatches) -> Config {
let file: Option<String> = match options.values_of(options::FILE) {
Some(mut values) => {
let name = values.next().unwrap();
if values.len() != 0 {
crash!(3, "extra operand {}", name);
}
if name == "-" {
None
} else {
if !Path::exists(Path::new(name)) {
crash!(2, "{}: No such file or directory", name);
}
Some(name.to_owned())
}
}
None => None,
};
let cols = match options.value_of(options::WRAP) {
Some(num) => match num.parse::<usize>() {
Ok(n) => Some(n),
Err(e) => {
crash!(1, "invalid wrap size: {}: {}", num, e);
}
},
None => None,
};
Config {
decode: options.is_present(options::DECODE),
ignore_garbage: options.is_present(options::IGNORE_GARBAGE),
wrap_cols: cols,
to_read: file,
}
}
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let format = Format::Base32;
let usage = get_usage();
let app = App::new(executable!())
.version(VERSION)
.about(SUMMARY)
.usage(&usage[..])
.about(LONG_HELP)
// Format arguments.
.arg(
Arg::with_name(options::DECODE)
.short("d")
.long(options::DECODE)
.help("decode data"),
)
.arg(
Arg::with_name(options::IGNORE_GARBAGE)
.short("i")
.long(options::IGNORE_GARBAGE)
.help("when decoding, ignore non-alphabetic characters"),
)
.arg(
Arg::with_name(options::WRAP)
.short("w")
.long(options::WRAP)
.takes_value(true)
.help(
"wrap encoded lines after COLS character (default 76, 0 to disable wrapping)",
),
)
// "multiple" arguments are used to check whether there is more than one
// file passed in.
.arg(Arg::with_name(options::FILE).index(1).multiple(true));
let name = executable!();
let arg_list = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let config: Config = Config::from(app.get_matches_from(arg_list));
match config.to_read {
// Read from file.
Some(name) => {
let file_buf = safe_unwrap!(File::open(Path::new(&name)));
let mut input = BufReader::new(file_buf);
base_common::handle_input(
&mut input,
format,
config.wrap_cols,
config.ignore_garbage,
config.decode,
);
let config_result: Result<base_common::Config, String> =
base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage);
if config_result.is_err() {
match config_result {
Ok(_) => panic!(),
Err(s) => crash!(BASE_CMD_PARSE_ERROR, "{}", s),
}
// stdin
None => {
base_common::handle_input(
&mut stdin().lock(),
format,
config.wrap_cols,
config.ignore_garbage,
config.decode,
);
}
};
}
// Create a reference to stdin so we can return a locked stdin from
// parse_base_cmd_args
let stdin_raw = stdin();
let config = config_result.unwrap();
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw);
base_common::handle_input(
&mut input,
format,
config.wrap_cols,
config.ignore_garbage,
config.decode,
name,
);
0
}

View file

@ -10,6 +10,122 @@
use std::io::{stdout, Read, Write};
use uucore::encoding::{wrap_print, Data, Format};
use uucore::InvalidEncodingHandling;
use std::fs::File;
use std::io::{BufReader, Stdin};
use std::path::Path;
use clap::{App, Arg};
// Config.
pub struct Config {
pub decode: bool,
pub ignore_garbage: bool,
pub wrap_cols: Option<usize>,
pub to_read: Option<String>,
}
pub mod options {
pub static DECODE: &str = "decode";
pub static WRAP: &str = "wrap";
pub static IGNORE_GARBAGE: &str = "ignore-garbage";
pub static FILE: &str = "file";
}
impl Config {
fn from(options: clap::ArgMatches) -> Result<Config, String> {
let file: Option<String> = match options.values_of(options::FILE) {
Some(mut values) => {
let name = values.next().unwrap();
if values.len() != 0 {
return Err(format!("extra operand {}", name));
}
if name == "-" {
None
} else {
if !Path::exists(Path::new(name)) {
return Err(format!("{}: No such file or directory", name));
}
Some(name.to_owned())
}
}
None => None,
};
let cols = match options.value_of(options::WRAP) {
Some(num) => match num.parse::<usize>() {
Ok(n) => Some(n),
Err(e) => {
return Err(format!("Invalid wrap size: {}: {}", num, e));
}
},
None => None,
};
Ok(Config {
decode: options.is_present(options::DECODE),
ignore_garbage: options.is_present(options::IGNORE_GARBAGE),
wrap_cols: cols,
to_read: file,
})
}
}
pub fn parse_base_cmd_args(
args: impl uucore::Args,
name: &str,
version: &str,
about: &str,
usage: &str,
) -> Result<Config, String> {
let app = App::new(name)
.version(version)
.about(about)
.usage(&usage[..])
// Format arguments.
.arg(
Arg::with_name(options::DECODE)
.short("d")
.long(options::DECODE)
.help("decode data"),
)
.arg(
Arg::with_name(options::IGNORE_GARBAGE)
.short("i")
.long(options::IGNORE_GARBAGE)
.help("when decoding, ignore non-alphabetic characters"),
)
.arg(
Arg::with_name(options::WRAP)
.short("w")
.long(options::WRAP)
.takes_value(true)
.help(
"wrap encoded lines after COLS character (default 76, 0 to disable wrapping)",
),
)
// "multiple" arguments are used to check whether there is more than one
// file passed in.
.arg(Arg::with_name(options::FILE).index(1).multiple(true));
let arg_list = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
Config::from(app.get_matches_from(arg_list))
}
pub fn get_input<'a>(config: &Config, stdin_ref: &'a Stdin) -> Box<dyn Read + 'a> {
match &config.to_read {
Some(name) => {
let file_buf = safe_unwrap!(File::open(Path::new(name)));
Box::new(BufReader::new(file_buf)) // as Box<dyn Read>
}
None => {
Box::new(stdin_ref.lock()) // as Box<dyn Read>
}
}
}
pub fn handle_input<R: Read>(
input: &mut R,
@ -17,6 +133,7 @@ pub fn handle_input<R: Read>(
line_wrap: Option<usize>,
ignore_garbage: bool,
decode: bool,
name: &str,
) {
let mut data = Data::new(input, format).ignore_garbage(ignore_garbage);
if let Some(wrap) = line_wrap {
@ -31,10 +148,14 @@ pub fn handle_input<R: Read>(
Ok(s) => {
if stdout().write_all(&s).is_err() {
// on windows console, writing invalid utf8 returns an error
crash!(1, "Cannot write non-utf8 data");
eprintln!("{}: error: Cannot write non-utf8 data", name);
exit!(1)
}
}
Err(_) => crash!(1, "invalid input"),
Err(_) => {
eprintln!("{}: error: invalid input", name);
exit!(1)
}
}
}
}

View file

@ -18,6 +18,7 @@ path = "src/base64.rs"
clap = "2.33"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
uu_base32 = { version=">=0.0.6", package="uu_base32", path="../base32"}
[[bin]]
name = "base64"

View file

@ -9,20 +9,13 @@
#[macro_use]
extern crate uucore;
use uu_base32::base_common;
use uucore::encoding::Format;
use uucore::InvalidEncodingHandling;
use std::fs::File;
use std::io::{stdin, BufReader};
use std::path::Path;
use std::io::{stdin, Read};
use clap::{App, Arg};
mod base_common;
static BASE64_ARG_ERROR: i32 = 1;
static SUMMARY: &str = "Base64 encode or decode FILE, or standard input, to standard output.";
static LONG_HELP: &str = "
static ABOUT: &str = "
With no FILE, or when FILE is -, read standard input.
The data are encoded as described for the base64 alphabet in RFC
@ -33,126 +26,40 @@ static LONG_HELP: &str = "
";
static VERSION: &str = env!("CARGO_PKG_VERSION");
static BASE_CMD_PARSE_ERROR: i32 = 1;
fn get_usage() -> String {
format!("{0} [OPTION]... [FILE]", executable!())
}
pub mod options {
pub static DECODE: &str = "decode";
pub static WRAP: &str = "wrap";
pub static IGNORE_GARBAGE: &str = "ignore-garbage";
pub static FILE: &str = "file";
}
struct Config {
decode: bool,
ignore_garbage: bool,
wrap_cols: Option<usize>,
to_read: Option<String>,
}
impl Config {
fn from(options: clap::ArgMatches) -> Config {
let file: Option<String> = match options.values_of(options::FILE) {
Some(mut values) => {
let name = values.next().unwrap();
if values.len() != 0 {
crash!(BASE64_ARG_ERROR, "extra operand {}", name);
}
if name == "-" {
None
} else {
if !Path::exists(Path::new(name)) {
crash!(BASE64_ARG_ERROR, "{}: No such file or directory", name);
}
Some(name.to_owned())
}
}
None => None,
};
let cols = match options.value_of(options::WRAP) {
Some(num) => match num.parse::<usize>() {
Ok(n) => Some(n),
Err(e) => {
crash!(BASE64_ARG_ERROR, "invalid wrap size: {}: {}", num, e);
}
},
None => None,
};
Config {
decode: options.is_present(options::DECODE),
ignore_garbage: options.is_present(options::IGNORE_GARBAGE),
wrap_cols: cols,
to_read: file,
}
}
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let format = Format::Base64;
let usage = get_usage();
let app = App::new(executable!())
.version(VERSION)
.about(SUMMARY)
.usage(&usage[..])
.about(LONG_HELP)
// Format arguments.
.arg(
Arg::with_name(options::DECODE)
.short("d")
.long(options::DECODE)
.help("decode data"),
)
.arg(
Arg::with_name(options::IGNORE_GARBAGE)
.short("i")
.long(options::IGNORE_GARBAGE)
.help("when decoding, ignore non-alphabetic characters"),
)
.arg(
Arg::with_name(options::WRAP)
.short("w")
.long(options::WRAP)
.takes_value(true)
.help(
"wrap encoded lines after COLS character (default 76, 0 to disable wrapping)",
),
)
// "multiple" arguments are used to check whether there is more than one
// file passed in.
.arg(Arg::with_name(options::FILE).index(1).multiple(true));
let name = executable!();
let config_result: Result<base_common::Config, String> =
base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage);
let arg_list = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let config: Config = Config::from(app.get_matches_from(arg_list));
match config.to_read {
// Read from file.
Some(name) => {
let file_buf = safe_unwrap!(File::open(Path::new(&name)));
let mut input = BufReader::new(file_buf);
base_common::handle_input(
&mut input,
format,
config.wrap_cols,
config.ignore_garbage,
config.decode,
);
if config_result.is_err() {
match config_result {
Ok(_) => panic!(),
Err(s) => crash!(BASE_CMD_PARSE_ERROR, "{}", s),
}
// stdin
None => {
base_common::handle_input(
&mut stdin().lock(),
format,
config.wrap_cols,
config.ignore_garbage,
config.decode,
);
}
};
}
// Create a reference to stdin so we can return a locked stdin from
// parse_base_cmd_args
let stdin_raw = stdin();
let config = config_result.unwrap();
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw);
base_common::handle_input(
&mut input,
format,
config.wrap_cols,
config.ignore_garbage,
config.decode,
name,
);
0
}

View file

@ -1,40 +0,0 @@
// This file is part of the uutils coreutils package.
//
// (c) Jordy Dickinson <jordy.dickinson@gmail.com>
// (c) Jian Zeng <anonymousknight96@gmail.com>
// (c) Alex Lyon <arcterus@mail.com>
//
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
use std::io::{stdout, Read, Write};
use uucore::encoding::{wrap_print, Data, Format};
pub fn handle_input<R: Read>(
input: &mut R,
format: Format,
line_wrap: Option<usize>,
ignore_garbage: bool,
decode: bool,
) {
let mut data = Data::new(input, format).ignore_garbage(ignore_garbage);
if let Some(wrap) = line_wrap {
data = data.line_wrap(wrap);
}
if !decode {
let encoded = data.encode();
wrap_print(&data, encoded);
} else {
match data.decode() {
Ok(s) => {
if stdout().write_all(&s).is_err() {
// on windows console, writing invalid utf8 returns an error
crash!(1, "Cannot write non-utf8 data");
}
}
Err(_) => crash!(1, "invalid input"),
}
}
}

View file

@ -98,7 +98,7 @@ fn test_wrap_bad_arg() {
.arg(wrap_param)
.arg("b")
.fails()
.stderr_only("base32: error: invalid wrap size: b: invalid digit found in string\n");
.stderr_only("base32: error: Invalid wrap size: b: invalid digit found in string\n");
}
}

View file

@ -88,7 +88,7 @@ fn test_wrap_bad_arg() {
.arg(wrap_param)
.arg("b")
.fails()
.stderr_only("base64: error: invalid wrap size: b: invalid digit found in string\n");
.stderr_only("base64: error: Invalid wrap size: b: invalid digit found in string\n");
}
}