我第一次进军铁锈。我想复制一个有趣和非功利主义的工具,所以我选择了CowSay。它的功能还没有完成(CowSay有很多选项),但这是一个开始。
我问这个问题的主要原因是:
chunk_args和multi_line函数的实现(要么改进它们,要么让我知道它们是XY思想的牺牲品)(该代码可在github上获得,以方便您进行克隆)
main.rs
extern crate getopts;
use std::io::prelude::*;
use std::cmp;
use getopts::Options;
const PROGRAM_NAME: &'static str = "rsay";
const COW: &'static str = include_str!("cow.txt");
const DEFAULT_LINE_WIDTH: usize = 40;
fn print_usage (opts: Options) {
let brief = format!("Usage: {} [-OPTIONS] [ARG...]", PROGRAM_NAME);
print!("{}", opts.usage(&brief));
}
fn parse_numeric(value: String, default: usize) -> usize {
match value.parse::<usize>() {
Ok(n) => { n },
Err(_) => { default },
}
}
// Ensure that lines have a maximum length of max_size
// and that there is a one space buffer between args
// e.g. with a `max_width` of five, the phrase "prolong a hat bear"
// should be split like so:
// ["prolon", "ng a", "hat", "bear"]
fn chunk_args (args: Vec<String>, max_size: usize) -> Vec<String> {
let mut lines = Vec::with_capacity(args.len() * 2);
let remainder: String = args.iter()
.fold(String::new(), |mut acc, arg| {
if !acc.is_empty() {
if (arg.chars().count() + 1) + acc.chars().count() <= max_size {
return acc + " " + arg;
} else {
lines.push(acc.clone());
acc.clear();
}
}
for c in arg.chars() {
acc.push(c);
if acc.chars().count() == max_size {
lines.push(acc.clone());
acc.clear();
}
}
acc
});
if !remainder.is_empty() {
lines.push(remainder);
}
lines
}
// Add the proper border to each line
// ["first", "mid", "last"]
//
// / first \
// | mid |
// \ last /
fn multi_line (lines: Vec<String>, width: usize) -> String {
let total_length = lines.len() - 1;
let formatted_lines = lines
.iter()
.enumerate()
.map(|(idx, line)| {
let current_length = line.clone().chars().count();
let padding: String = (0..width - current_length).map(|_| ' ').collect();
let (start, end) = match idx {
0 => ('/', '\\'),
_ if idx == total_length => ('\\', '/'),
_ => ('|', '|'),
};
format!("{} {}{} {}\n", start, line, padding, end)
});
formatted_lines.collect()
}
fn say (args: Vec<String>, desired_width: usize) -> String {
let chunks = chunk_args(args, desired_width);
let largest_str = chunks.iter().map(|x| x.chars().count()).max();
let width = match largest_str {
Some(x) => { cmp::min(desired_width, x) },
_ => { desired_width }
};
let formatted = match chunks.len() {
1 => format!("< {} >\n", chunks.join(" ")),
_ => multi_line(chunks, width),
};
let top_border: String = (0..width + 2).map(|_| "_").collect();
let bottom_border: String = (0..width + 2).map(|_| "-").collect();
format!(" {}\n{} {}", top_border, formatted, bottom_border)
}
fn main () {
let args: Vec<String> = std::env::args()
.skip(1)
.collect();
let mut opts = Options::new();
opts.optflag("h", "help", "Print this help menu");
opts.optmulti("W", "width", "Width of output", "50");
let matches = match opts.parse(&args) {
Ok(m) => { m }
Err(f) => { panic!(f.to_string()) }
};
if matches.opt_present("h") {
print_usage(opts);
return;
}
let width = match matches.opt_str("W") {
None => { DEFAULT_LINE_WIDTH },
Some(w) => { parse_numeric(w, DEFAULT_LINE_WIDTH) }
};
let input = if !matches.free.is_empty() {
matches.free
} else {
print_usage(opts);
return;
};
print!("{}\n{}", say(input, width), COW);
}小测试集:
#[cfg(test)]
#[test]
fn test_chunk_args_padding () {
let phrase = ["broken", "big", "bar"].iter().map(|&x| x.into()).collect();
let result = chunk_args(phrase, 5);
assert_eq!(vec!["broke".to_string(), "n big".into(), "bar".into()], result);
}
#[test]
fn test_say_multi_line () {
let args = ["broke", "n big", "bar"].iter().map(|&x| x.into()).collect();
let result = say(args, 5);
let expected: String = r" _______
/ broke \
| n big |
\ bar /
-------".into();
assert_eq!(expected, result);
}
#[test]
fn test_say_multi_line_wide () {
let phrase = "aggregate rotor hat".split(" ").map(|x| x.into()).collect();
let result = chunk_args(phrase, 10);
assert_eq!(vec!["aggregate", "rotor hat"], result);
}
#[test]
fn test_say_single_line () {
let args = ["foo", "bar", "baz"].iter().map(|&x| x.into()).collect();
let result = say(args, 40);
let expected: String = r" _____________
< foo bar baz >
-------------".into();
assert_eq!(expected, result);
}cow.txt
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||输出
$ rsay hello world
_____________
< hello world >
-------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| || 发布于 2016-07-26 16:54:06
(left == right) (左:" \_\_\_\_\_\_\_\n/ broke \\\n| n big |\n\\ bar /\n-------",右:" \_\_\_\_\_\_\_\n/ broke \\\n| n big |\n\\ bar /\n -------"),src/main.rs:153 -- test_say_single_line stdout --线程'test_say_single_line’在断言失败时惊慌失措:(left == right) (左:" \_\_\_\_\_\_\_\_\_\_\_\_\_\n< foo bar baz >\n-------------",右:" \_\_\_\_\_\_\_\_\_\_\_\_\_\n< foo bar baz >\n -------------")',src/main.rs:171char的宽度为1. 零宽度空间不同意,并且存在可能代码点比一个更宽。Copy类型的引用,除非代码直接受益于所有权的转移。例如,接受&str而不是String和&[T]而不是Vec<T>。这些更灵活。Vec<String>的建议结合起来要复杂一些;相反,代码可以接受任何使用AsRef<str>看起来像&str的代码。注意,这样做简化了测试,避免了额外的分配。parse上在parse_numeric中指定类型;它是从函数返回类型推断出来的。Option和Result上的方法。unwrap_or。map。map_or。Options或Results链接在一起的是and_then。unwrap或更好的expect会引起恐慌。parse_numeric就会有可疑的好处。我会内联它,这也避免了重复默认值。///)。文档就是标记,所以使用像foo这样的东西来一致地突出代码和参数。chunk_args的文档说明了“最大宽度为5”会产生一个6个字符的字符串。延长的"ng“被重复。chunk_args提供了一个字符串,但它似乎是一个Vec<String>?rsay "moo alot more than that" --width=10处理得不太好。total_length的名字似乎不正确;应该类似于last_index。clone。repeat函数。我喜欢这样做,因为它避免了在所有调用站点指定对String的收集。repeat而不是mapping一个范围,但我不确定这有什么关系。Vec子元素的类型;可以推断。is_empty的逻辑;它可能只是一个早期退出。#[cfg(test)]并没有真正做任何事情;它只应用于下一项。下一个项目恰好是一个#[test]函数,它将在不以测试模式编译时自动删除。通常,cfg属性应用于test模块。String和&str可以直接比较;不需要分配。extern crate getopts;
use std::cmp;
use getopts::Options;
const PROGRAM_NAME: &'static str = "rsay";
const COW: &'static str = include_str!("cow.txt");
const DEFAULT_LINE_WIDTH: usize = 40;
fn print_usage(opts: Options) {
let brief = format!("Usage: {} [-OPTIONS] [ARG...]", PROGRAM_NAME);
print!("{}", opts.usage(&brief));
}
/// Ensure that lines have a maximum length of `max_size` and that
/// there is a one space buffer between args.
fn chunk_args<S>(args: &[S], max_size: usize) -> Vec<String>
where S: AsRef<str>,
{
let mut lines = Vec::with_capacity(args.len() * 2);
let remainder: String = args.iter()
.fold(String::new(), |mut acc, arg| {
let arg = arg.as_ref();
if !acc.is_empty() {
if (arg.chars().count() + 1) + acc.chars().count() <= max_size {
return acc + " " + arg;
} else {
lines.push(acc.clone());
acc.clear();
}
}
for c in arg.chars() {
acc.push(c);
if acc.chars().count() == max_size {
lines.push(acc.clone());
acc.clear();
}
}
acc
});
if !remainder.is_empty() {
lines.push(remainder);
}
lines
}
fn repeat(s: &str, len: usize) -> String {
::std::iter::repeat(s).take(len).collect()
}
/// Add the proper border to each line.
///
/// ["first", "mid", "last"] would become
///
/// / first \
/// | mid |
/// \ last /
fn multi_line<S>(lines: &[S], width: usize) -> String
where S: AsRef<str>,
{
let last_index = lines.len() - 1;
let formatted_lines = lines.iter()
.enumerate()
.map(|(idx, line)| {
let line = line.as_ref();
let current_length = line.chars().count();
let padding = repeat(" ", width - current_length);
let (start, end) = match idx {
0 => ('/', '\\'),
_ if idx == last_index => ('\\', '/'),
_ => ('|', '|'),
};
format!("{} {}{} {}\n", start, line, padding, end)
});
formatted_lines.collect()
}
fn say<S>(args: &[S], desired_width: usize) -> String
where S: AsRef<str>,
{
let chunks = chunk_args(args, desired_width);
let largest_str = chunks.iter().map(|x| x.chars().count()).max();
let width = largest_str.map_or(desired_width, |x| cmp::min(desired_width, x));
let formatted = match chunks.len() {
1 => format!("< {} >\n", chunks.join(" ")),
_ => multi_line(&chunks, width),
};
let top_border = repeat("_", width + 2);
let bottom_border = repeat("-", width + 2);
format!(" {}\n{} {}", top_border, formatted, bottom_border)
}
fn main() {
let args: Vec<_> = std::env::args()
.skip(1)
.collect();
let mut opts = Options::new();
opts.optflag("h", "help", "Print this help menu");
opts.optmulti("W", "width", "Width of output", "50");
let matches = opts.parse(&args).expect("Unable to parse arguments");
if matches.opt_present("h") {
print_usage(opts);
return;
}
let width = matches.opt_str("W")
.and_then(|w| w.parse().ok())
.unwrap_or(DEFAULT_LINE_WIDTH);
if matches.free.is_empty() {
print_usage(opts);
return;
};
let input = matches.free;
print!("{}\n{}", say(&input, width), COW);
}
#[cfg(test)]
mod test {
use super::{chunk_args, say};
#[test]
fn test_chunk_args_padding() {
let phrase = &["broken", "big", "bar"];
let result = chunk_args(phrase, 5);
assert_eq!(vec!["broke".to_string(), "n big".into(), "bar".into()], result);
}
#[test]
fn test_say_multi_line() {
let args = &["broke", "n big", "bar"];
let result = say(args, 5);
let expected = r" _______
/ broke \
| n big |
\ bar /
-------";
assert_eq!(expected, result);
}
#[test]
fn test_say_multi_line_wide() {
let phrase = &["aggregate", "rotor", "hat"];
let result = chunk_args(phrase, 10);
assert_eq!(vec!["aggregate", "rotor hat"], result);
}
#[test]
fn test_say_single_line() {
let args = &["foo", "bar", "baz"];
let result = say(args, 40);
let expected = r" _____________
< foo bar baz >
-------------";
assert_eq!(expected, result);
}
}在OS上运行
cargo build时,我没有看到编译器的警告;在构建Debian时,我确实看到了编译器的警告。我该怎么避免呢?
这很奇怪,因为我在OS上遇到了错误。也许这两台机器上有不同的Rust版本?
https://codereview.stackexchange.com/questions/135796
复制相似问题