目标是编写一个获取两个路径input_dir
和output_dir
的函数,并将input_dir
中的所有降价文件转换为output_dir
中的html文件。
我终于设法让它运行,但它相当令人沮丧。应该很难的部分非常简单:从Markdown到HTML的实际转换实际上只有一行。看似简单的部分是我花费最长的时间。使用路径向量并将所有文件放入其中是我用glob
箱子替换的东西。不是因为我无法让它工作,而是if let
和unwrap
混乱。一个简单的函数,迭代元素列表并确定哪些是实际文件而不是目录?如果if let
或者我对match
es感到惊讶,我需要四个缩进级别。
我做错了什么?
但是让我们从一些事情开始,我试图获取一个目录中的项目列表,过滤到只包含实际文件:
use std::fs;
use std::vec::Vec;
fn list_files (path: &str) -> Result<Vec<&str>, &str> {
if let Ok(dir_list) = fs::read_dir(path) {
Ok(dir_list.filter_map(|e| {
match e {
Ok(entry) => match entry.file_type() {
Ok(_) => entry.file_name().to_str(),
_ => None
},
_ => None
}
}).collect())
} else {
Err("nope")
}
}
fn main() {
let files = list_files("testdir");
println!("{:?}", files.unwrap_or(Vec::new()));
}
因此,此代码不会构建,因为第10行中的文件名不能存活足够长的时间。我想我可以以某种方式创建一个拥有的String
,但这会引入另一个嵌套级别,因为OsStr.to_string()
会返回Result
。
现在我查看了glob
crate的代码,他们只是使用了一个可变的向量:
fn list_files (path: &str) -> Result<Vec<&str>, &str> {
let mut list = Vec::new();
if let Ok(dir_list) = fs::read_dir(path) {
for entry in dir_list {
if let Ok(entry) = entry {
if let Ok(file_type) = entry.file_type() {
if file_type.is_file() {
if let Some(name) = entry.file_name().to_str() {
list.push(name)
}
}
}
}
}
Ok(list)
} else {
Err("nope")
}
}
这不仅增加了疯狂的嵌套,它也失败了同样的问题。如果我从Vec<&str>
更改为Vec<String>
,则可以:
fn list_files (path: &str) -> Result<Vec<String>, &str> {
let mut list = Vec::new();
if let Ok(dir_list) = fs::read_dir(path) {
for entry in dir_list {
if let Ok(entry) = entry {
if let Ok(file_type) = entry.file_type() {
if file_type.is_file() {
if let Ok(name) = entry.file_name().into_string() {
list.push(name)
}
}
}
}
}
Ok(list)
} else {
Err("nope")
}
}
看起来我应该在第一次尝试时使用它,对吧?
fn list_files (path: &str) -> Result<Vec<String>, &str> {
if let Ok(dir_list) = fs::read_dir(path) {
Ok(dir_list.filter_map(|e| {
match e {
Ok(entry) => match entry.file_type() {
Ok(_) => Some(entry.file_name().into_string().ok()),
_ => None
},
_ => None
}
}).collect())
} else {
Err("nope")
}
}
至少有点短......但是无法编译,因为类型std::vec::Vec<std::string::String>
的集合无法通过类型为std::option::Option<std::string::String>
的元素从迭代器构建。
这里很难保持耐心。为什么.filter_map
会返回Option
而不是仅使用它们进行过滤?现在我必须将第15行从}).collect())
更改为}).map(|e| e.unwrap()).collect())
,再次在结果集上再次迭代。
那可能是对的!
答案 0 :(得分:1)
您可以大量依赖?
operator:
use std::fs;
use std::io::{Error, ErrorKind};
fn list_files(path: &str) -> Result<Vec<String>, Error> {
let mut list = Vec::new();
for entry in fs::read_dir(path)? {
let entry = entry?;
if entry.file_type()?.is_file() {
list.push(entry.file_name().into_string().map_err(|_| {
Error::new(ErrorKind::InvalidData, "Cannot convert file name")
})?)
}
}
Ok(list)
}
不要忘记您可以将代码拆分为函数或实现自己的trait
以简化最终代码:
use std::fs;
use std::io::{Error, ErrorKind};
trait CustomGetFileName {
fn get_file_name(self) -> Result<String, Error>;
}
impl CustomGetFileName for std::fs::DirEntry {
fn get_file_name(self) -> Result<String, Error> {
Ok(self.file_name().into_string().map_err(|_|
Error::new(ErrorKind::InvalidData, "Cannot convert file name")
)?)
}
}
fn list_files(path: &str) -> Result<Vec<String>, Error> {
let mut list = Vec::new();
for entry in fs::read_dir(path)? {
let entry = entry?;
if entry.file_type()?.is_file() {
list.push(entry.get_file_name()?)
}
}
Ok(list)
}
答案 1 :(得分:1)
迭代器的替代答案playground
use std::fs;
use std::error::Error;
use std::path::PathBuf;
fn list_files(path: &str) -> Result<Vec<PathBuf>, Box<Error>> {
let x = fs::read_dir(path)?
.filter_map(|e| e.ok())
.filter(|e| e.metadata().is_ok())
.filter(|e| e.metadata().unwrap().is_file())
.map(|e| e.path())
.collect();
Ok(x)
}
fn main() {
let path = ".";
for res in list_files(path).unwrap() {
println!("{:#?}", res);
}
}