为了好玩,我重新实现了cat(1)
。我遵循的是开放组基规范(2018年年版第7期),而不是GNU变体及其命令行参数。
-u
n缓冲行为虽然规范定义了-u
的行为,但它没有定义在缺少-u
nbuffered参数时如何连接参数。为了适应BufRead
和BufWriter
,我使用了一种缓冲方法,它只使用Rust已经存在的方法。
我对返回代码问题并不完全满意。目前,我在io::Result
中使用main
返回最后一个错误,但是,这也意味着最后一个错误将被报告两次。我可以使用std::process::exit
,但这需要main
的包装器。
我承认,main
的争论处理部分不会赢得选美比赛。但是,我不想添加clap
或其他参数处理库,而是专注于使用std
。程序应该遵循效用参数语法,但是它不遵循准则5(例如,-uuuu
与-u -u -u -u
不同)和9 (-u
不需要作为第一个参数)。然而,参数分组是这个玩具程序的一个非目标。
此外,与GNU cat
相比,未知选项被解释为文件名,而GNU cat
将带错误消息退出。不过,我不确定这是否违反了规范。
应用程序分为两部分,即main.rs
(主要是参数解析)和lib.rs
(实际实现)。对该组织的任何评论都是可以的。
// main.rs
use std::ffi::OsString;
use std::path::Path;
use cat::{cat_buffered_single, cat_unbuffered_single};
fn main() -> std::io::Result<()> {
let mut args: Vec<OsString> = std::env::args_os().skip(1).collect();
// Only parse arguments up to "--"
let args_up_to = if let Some(index) = args.iter().position(|arg| arg == "--") {
args.remove(index);
index
} else {
args.len()
};
// Keep all arguments after "--" as-is
let verbatim_args = args.split_off(args_up_to);
// Check for "-u" in valid positions and remove the first one
let cat_func = if args.iter().any(|arg| arg == "-u") {
args = args.into_iter().filter(|x| x != "-u").collect();
cat_unbuffered_single
} else {
cat_buffered_single
};
// Recombine arguments
args.extend(verbatim_args);
// Fallback to stdin behaviour
if args.is_empty() {
args.push("-".into());
}
let mut result = Ok(());
for arg in args {
let path = Path::new(&arg);
match cat_func(path) {
Ok(()) => continue,
Err(e) => {
eprintln!("cat: {}: {}", path.to_string_lossy(), e);
result = Err(e);
}
}
}
result
}
// lib.rs
use std::fs::File;
use std::io::{self, BufRead, BufReader, BufWriter, Read, Write};
use std::path::Path;
// Dumps all bytes from `src` into `dest`, using both buffer functionalities.
fn dump_buffered_single(src: &mut dyn BufRead, dest: &mut BufWriter<impl Write>) -> io::Result<()> {
loop {
let buf = src.fill_buf()?;
if buf.is_empty() {
break;
}
dest.write_all(buf)?;
let bytes = buf.len();
src.consume(bytes);
}
dest.flush()
}
/// Dumps the file given by `path` on `stdout` using buffered IO.
///
/// If `path` is `"-"`, then `stdin` is used as input instead of a file.
///
/// Example
/// ```
/// # use cat::cat_buffered_single;
/// cat_buffered_single("hello.txt".as_ref());
/// ```
pub fn cat_buffered_single(path: &Path) -> io::Result<()> {
let stdout = io::stdout();
let handle = stdout.lock();
let mut writer = io::BufWriter::new(handle);
if path == Path::new("-") {
dump_buffered_single(&mut io::stdin().lock(), &mut writer)?;
} else {
let input = BufReader::new(File::open(path)?);
let mut reader = BufReader::new(input);
dump_buffered_single(&mut reader, &mut writer)?;
}
Ok(writer.flush()?)
}
// Dumps all bytes from `src` into `dest`, byte by byte.
fn dump_unbuffered_single(src: &mut dyn Read, dest: &mut dyn Write) -> io::Result<()> {
for byte in src.bytes() {
dest.write_all(std::slice::from_ref(&byte?))?;
}
Ok(())
}
/// Dumps the file given by `path` on `stdout` without buffering
///
/// If `path` is `"-"`, then `stdin` is used as input instead of a file.
///
/// Example
/// ```
/// # use cat::cat_unbuffered_single;
/// cat_unbuffered_single("hello.txt".as_ref());
/// ```
pub fn cat_unbuffered_single(path: &Path) -> io::Result<()> {
let stdout = io::stdout();
let mut handle = stdout.lock();
if path == Path::new("-") {
dump_unbuffered_single(&mut io::stdin().lock(), &mut handle)?;
} else {
let mut file = File::open(path)?;
dump_unbuffered_single(&mut file, &mut handle)?;
}
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
fn test_dump_buffered_single(test_bytes: &[u8]) {
let src = test_bytes;
let mut reader = BufReader::new(src);
let mut dest = vec![];
{
let mut writer = BufWriter::new(&mut dest);
dump_buffered_single(&mut reader, &mut writer).unwrap();
}
assert_eq!(dest, src);
}
#[test]
fn dumps_all_buffered_data() {
test_dump_buffered_single(b"Hello, World");
}
#[test]
fn dumps_no_buffered_data() {
test_dump_buffered_single(b"");
}
fn test_dump_unbuffered_single(bytes: &[u8]) {
let source = bytes;
let mut reader = source.clone();
let mut destination = vec![];
dump_unbuffered_single(&mut reader, &mut destination).unwrap();
assert_eq!(destination, source);
}
#[test]
fn dumps_all_unbuffered_data() {
test_dump_unbuffered_single(b"Hello, World");
}
#[test]
fn dumps_no_unbuffered_data() {
test_dump_unbuffered_single(b"");
}
}
我在上面的代码中使用了cargo fmt
和cargo clippy
。顺便说一句,我认为自己是个生锈初学者,所以可以随意评论代码的任何部分。
发布于 2020-08-11 07:49:29
首先,我想指出,以一致的格式、有帮助的注释和清晰的逻辑阅读代码是非常高兴的。以下几点可能是主观的和挑剔的,但它们并不代表我的一般印象。
程序应该遵循效用参数语法,但是它不遵循... 9准则(
-u
不需要成为第一个参数)。
但是,要求-u
出现在操作数之前,不会使实现变得更容易吗?我是这样想的:
let (buffered, operands) = match args.get(0) {
None => {
args.push("-".into());
(true, &args[..])
}
Some(arg) if *arg == "-u" => (false, &args[1..]),
Some(_) => (true, &args[..]),
};
BufRead
和Write
与&mut dyn BufRead
不同,按值取BufRead
更为常见。原因是对BufRead
的可变引用自动实现了BufRead
。
不使用&mut BufWriter<impl Write>
类型的参数,只需使用Write
就够了,因为BufWriter
的功能可以通过Write
访问。
结果:
fn dump_buffered_single<R, W>(mut src: R, mut dest: W) -> io::Result<()>
where
R: BufRead,
W: Write,
{
// ...
}
https://codereview.stackexchange.com/questions/247752
复制相似问题