#shortcuts #utility #command #directory #command-line-utilities #read-file #fs-file

easy-shortcuts

为简短的命令行程序提供便捷的辅助工具

3个版本 (破坏性更新)

使用旧的Rust 2015

0.3.0 2017年12月12日
0.2.0 2017年7月23日
0.1.0 2016年12月14日

#shortcuts中排名8


2 crates中使用

MIT许可协议

27KB
317

为小型程序提供的便利

旨在与其他系统命令良好配合的小程序需要尽早退出并返回非零退出代码。在这种情况下惊慌失措看起来很杂乱无章——它是开发者功能。

考虑一个需要读取命令行上指定文件的所有内容的小程序。

use std::io::prelude::*;
use std::fs::File;
use std::env;

let file = env::args().nth(1).expect("please supply a file name");

let mut f = File::open(&file).expect("can't open the file");
let mut s = String::new();
f.read_to_string(&mut s)).expect("can't read the string");

如果你只是想退出,这有点麻烦。

extern crate easy_shortcuts as es;

let file = es::argn_err(1,"please supply a file name");
let s = es::read_to_string(&file);

将无效的文件名传递给此程序将产生一个明确的致命错误

./read_all error: open 'bonzo.txt' No such file or directory (os error 2)

除了命令之外,还有另一类重要的小程序:当你探索API时编写的代码片段。当你只想玩文件内容时,编写样板代码很烦人。

另一个常见的需求是迭代文件中的所有行

extern crate easy_shortcuts as es:
use std::io;

for line in es::lines(io.stdin()) {
	// do your thang!
}

这会创建一个io::BufReader并通过类似Java的仪式来迭代文件的所有行字符串;如果在迭代过程中发生I/O错误,它将退出。

迭代器快捷方式

有时你只想消耗一个迭代器并打印其值。这个小程序将简单地回显前十条stdin到stdout

use es::traits::*;

es::lines(io.stdin()).take(10).print("\n")

这里有一个半有用的例子。Unix配置文件通常有很多被注释掉的选择项;它只会打印出正在使用的选项

let conf = es::argn_err(1,"please provide a conf file");
es::lines(es::open(&conf))
    .filter(|s| s.len() > 0 && ! s.starts_with("#"))
    .print("\n");

还有与之相当的debug,这对于找出迭代器实际上在输出什么非常有用

(0..5).map(|n| (n,2*n)).debug("\n");
//->
(0, 0)
(1, 2)
(2, 4)
(3, 6)
(4, 8)

字符串方法,如skip_whitespace返回迭代器,easy_shortcuts提供了一些处理和收集字符串的便利

let strs = ["one","two","three"];
let s = strs.into_iter().prepend(" hello ");
assert_eq!(s," hello one hello two hello three ");

还有Python爱好者最喜欢的join。(它在字符串向量上定义,但作为迭代器方法,我们避免了不必要的临时对象创建。)

let s = "one two three".split_whitespace().join(',');
assert_eq!(s,"one,two,three");

collect被认为很烦人

迭代器有方便的to_vecto_map方法。 collect非常通用,其实现简单:任何实现了FromIterator特质的类型。通常你必须给出一些类型提示(Rust编译器还不是先知),但十有八九你想要一个Vec

let v: Vec<_> = (0..5).collect();
// easier to read and totally equivalent
let v = (0..5).to_vec();

这是一个快速读取配置文件的方法,其中键和值由'='分隔

	let map = es::lines(es::open(&conf))
      .filter(|s| ! s.starts_with("#")) // ignore commments
      .filter_map(|s| s.split_at_delim('=').trim()) // split into (String,String)
	  .to_map();

额外的字符串小工具

我忍不住添加了一些方便的字符串方法。

split_at_delim 类似于字符串方法 findsplit_at 的组合,但分隔符不会被包含;"one = two".split_at_delim('=') 的结果为 Some(("one "," two"))。函数 trim 对此函数的结果进行操作,将 Option<(&str,&str)> 转换为 Option<(String,String)>,并去除任何多余的空白字符,或者直接传递 None

在配置文件示例中,注意空行将被自动忽略,因为 split_at_delim 的结果将是 None,而 trim 会直接通过。

另一个便利之处是定义在字符串上的 is_whitespace。此示例统计源代码行和注释行,假设注释为 '//'。

extern crate easy_shortcuts as es;
use es::traits::*;

fn main() {
	let file = es::argn_err(1,"please provide a source file");
	let mut scount = 0;
	let mut ccount = 0;
	for line in es::lines(es::open(&file)) {
		if let Some(idx) = line.find("//") {
			// now look at everything up to //
			let start = &line[0..idx];
			if start.is_whitespace() {
				ccount += 1;
			}
		}
		scount += 1;	
	}
	println!("source lines {} comment lines {}",scount-ccount,ccount);
}

首选的解决方案是使用正则表达式,但 Rust 的字符串处理本身就非常出色 - 字符串切片是一个很棒的功能。

适用于 Option<T>Result<T,E> 的函数非常方便。例如,MetadataLike trait 添加了 fs::Metadata 的布尔方法。其理由是,通常您想知道目录项是否存在,并且它是否为所需类型。

let ok = fs::metadata(".").is_dir();

// which is short for:
let ok = match fs::metadata(".") {
	Ok(m) => m.is_dir(),
	Err(e) => false
}

目录遍历

通常需要查看目录内容 - 现有的 API 稍显笨拙,特别是如果您有一个“提前退出”策略。 paths 提供了一个迭代器,遍历目录,并返回路径及其相关元数据的元组。

extern crate easy_shortcuts as es;

fn has_extension(p: &std::path::Path, e: &str) -> bool {
	match p.extension() {
		Some(ext) => ext == e,
		None => false
	}
}

fn main() {
	for (path,data) in es::paths(".") {
		if data.is_file() && has_extension(&path,"rs") {
			println!("file {:?} len {}",path,data.len());
		}
	}
}

示例

run-test.rs 是一个我编写的小型但非平凡程序,它使用此 crate 来帮助我编写文档测试。因为(说实话)这是一个令人烦恼的过程;你将代码放入 注释 中,失去了所有那些来自语法高亮的美好视觉线索,而测试涉及整个 crate 的编译以及所有由文档测试生成的那些小 crate。有了这个小巧的工具,我可以将编写文档片段的时间从 20 秒缩短到 0.5 秒。

对于这个工作流程,在crate目录下创建一个子目录(我称之为scratch),并将其添加到.gitignore中。源文件遵循与doc测试本身相同的规则,在前面添加extern crate THIS_CRATE,并创建一个main函数。它通过将代码复制到examples目录并对其执行cargo run --example来编译和运行修改后的代码。

~/rust/easy-shortcuts/scratch$ cat one.rs
let s = easy_shortcuts::read_to_string("one.rs");
println!("{}",s);
~/rust/easy-shortcuts/scratch$ run-test one.rs

let s = easy_shortcuts::read_to_string("one.rs");
println!("{}",s);


/// ```
/// let s = easy_shortcuts::read_to_string("one.rs");
/// println!("{}",s);
/// ```

然后我们写入带有适当文档注释的代码片段。如果你运行时带有额外的'!'参数,则会创建模块文档注释(使用//!)。

下一个有用的小程序是crate.rs:给定一个crate名称,它将在Cargo的源缓存中查找并打印出该crate的最高版本的源目录。

~/rust/easy-shortcuts/examples$ cargo run -q --example crate semver
/home/steve/.cargo/registry/src/github.com-1ecc6299db9ec823/semver-0.2.3

它需要修改才能在Windows上工作,如果它是一个'真实'程序,它将引入对semver、glob和可能正则表达式的依赖。但它不是作为一个好的Rust应用程序风格的例子,而是一个使用easy_shortcuts的例子。

无运行时依赖