首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >CowSay的一个生疏的实现

CowSay的一个生疏的实现
EN

Code Review用户
提问于 2016-07-24 21:24:38
回答 1查看 330关注 0票数 8

我第一次进军铁锈。我想复制一个有趣和非功利主义的工具,所以我选择了CowSay。它的功能还没有完成(CowSay有很多选项),但这是一个开始。

我问这个问题的主要原因是:

  1. 更好地掌握惯用生锈(这是我在语言中写的第一件事)
  2. 改进chunk_argsmulti_line函数的实现(要么改进它们,要么让我知道它们是XY思想的牺牲品)

(该代码可在github上获得,以方便您进行克隆)

main.rs

代码语言:javascript
复制
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);
}

小测试集:

代码语言:javascript
复制
#[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

代码语言:javascript
复制
\   ^__^
 \  (oo)\_______
    (__)\       )\/\
        ||----w |
        ||     ||

输出

代码语言:javascript
复制
$ rsay hello world
 _____________                     
< hello world >                    
 -------------                     
       \   ^__^                   
        \  (oo)\_______           
           (__)\       )\/\       
               ||----w |          
               ||     ||          
EN

回答 1

Code Review用户

回答已采纳

发布于 2016-07-26 16:54:06

  1. 测试失败了。这不是件好事:-- test_say_multi_line stdout --当断言失败时线程'test_say_multi_line‘惊慌失措:(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:171
  2. 输出不像所描述的或预期的那样。我甚至没看到能把牛移过去的密码.$ rsay moo比那个多得多--宽度=10 ___________ / moo很多\ ^__^ \ (oo)\_______ (__)\ lot
  3. 有个警告。不要忽视警告,要修正它们。使用编译语言的原因之一是得到编译器的反馈。警告:未使用导入,#(_进口)) on默认使用std::io::prelude:*;^~
  4. 代码假设单个char的宽度为1. 零宽度空间不同意,并且存在可能代码点比一个更宽
  5. 在Unicode代码点上拆分字符串可能没有任何意义。例如,如果输入包含一个带有结合双晶的字母,那么在单独的行中拆分这些字母是没有意义的。或许Unicode规范化图形素的结合会有所帮助。
  6. 接受对非Copy类型的引用,除非代码直接受益于所有权的转移。例如,接受&str而不是String&[T]而不是Vec<T>。这些更灵活。
  7. 将上面对Vec<String>的建议结合起来要复杂一些;相反,代码可以接受任何使用AsRef<str>看起来像&str的代码。注意,这样做简化了测试,避免了额外的分配。
  8. 不需要在parse上在parse_numeric中指定类型;它是从函数返回类型推断出来的。
  9. 熟悉OptionResult上的方法。
    1. 提供默认的是unwrap_or
    2. 当值存在时,转换值是map
    3. 把它们结合在一起就是map_or
    4. 将多重Options或Results链接在一起的是and_then
    5. 当值丢失时,unwrap或更好的expect会引起恐慌。

  10. 一旦使用了这些方法,parse_numeric就会有可疑的好处。我会内联它,这也避免了重复默认值。
  11. 如果为使用者记录了一个函数,请使用Rustdoc注释(///)。文档就是标记,所以使用像foo这样的东西来一致地突出代码和参数。
  12. chunk_args的文档说明了“最大宽度为5”会产生一个6个字符的字符串。延长的"ng“被重复。
  13. 文档演示了chunk_args提供了一个字符串,但它似乎是一个Vec<String>
  14. 测试应该显示带有多个单词的单个字符串的情况;看起来rsay "moo alot more than that" --width=10处理得不太好。
  15. 尽可能避免重复函数参数和返回函数文档中的类型和测试/示例;代码不能像文档那样容易说谎。
  16. 记住,Rustdoc可以包含在编译时执行的代码。这通常用于内联测试,以确保示例不会腐烂。
  17. 函数名和括号之间没有空格。看看铁锈
  18. total_length的名字似乎不正确;应该类似于last_index
  19. 在计算字符时,有一个不需要的clone
  20. 提取一个repeat函数。我喜欢这样做,因为它避免了在所有调用站点指定对String的收集。
  21. 我通常会使用repeat而不是mapping一个范围,但我不确定这有什么关系。
  22. 在收集参数时,不需要指定Vec子元素的类型;可以推断。
  23. 简化检查is_empty的逻辑;它可能只是一个早期退出。
  24. #[cfg(test)]并没有真正做任何事情;它只应用于下一项。下一个项目恰好是一个#[test]函数,它将在不以测试模式编译时自动删除。通常,cfg属性应用于test模块。
  25. String&str可以直接比较;不需要分配。
代码语言:javascript
复制
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版本?

票数 9
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/135796

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档