use serde_derive::Serialize;
use std::fmt;
use std::fs::File;
use std::io::prelude::*;
use std::io::BufReader;
use rayon::prelude::*;
#[derive(Serialize)]
pub struct FileStats {
pub lines: Option<usize>,
pub words: Option<usize>,
pub chars: Option<usize>,
}
impl fmt::Display for FileStats {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}{}{}",
match self.lines {
Some(lines) => format!("{} ", lines),
None => String::new(),
},
match self.words {
Some(words) => format!("{} ", words),
None => String::new(),
},
match self.chars {
Some(chars) => format!("{} ", chars),
None => String::new(),
},
)
}
}
#[derive(Copy, Clone, Debug)]
pub struct AnalysisOptions {
pub lines: bool,
pub words: bool,
pub chars: bool,
}
#[derive(Serialize)]
#[serde(untagged)]
pub enum NamedOutput {
Success { filename: String, stats: FileStats },
Error { filename: String, error: String },
}
impl fmt::Display for NamedOutput {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
NamedOutput::Success { filename, stats } => write!(f, "{}{}", stats, filename),
NamedOutput::Error { filename, error } => write!(f, "{} {}", error, filename),
}
}
}
pub fn analyse_string(contents: &str, options: AnalysisOptions) -> FileStats {
FileStats {
lines: if options.lines {
Some(contents.lines().count())
} else {
None
},
words: if options.words {
Some(contents.split_whitespace().count())
} else {
None
},
chars: if options.chars {
Some(contents.chars().count())
} else {
None
},
}
}
pub fn analyse_file(filename: &str, options: AnalysisOptions) -> NamedOutput {
let file = match File::open(filename) {
Err(e) => {
return NamedOutput::Error {
filename: filename.to_string(),
error: e.to_string(),
}
}
Ok(f) => f,
};
let mut buf_reader = BufReader::new(file);
let mut contents = String::new();
match buf_reader.read_to_string(&mut contents) {
Ok(_bytes) => NamedOutput::Success {
filename: filename.to_string(),
stats: analyse_string(&contents, options),
},
Err(e) => NamedOutput::Error {
filename: filename.to_string(),
error: e.to_string(),
},
}
}
pub fn analyse_files(filenames: &[&str], options: AnalysisOptions) -> Vec<NamedOutput> {
filenames
.par_iter()
.map(|filename| analyse_file(filename, options))
.collect()
}