1use serde_derive::Serialize;
2use std::fmt;
3
4use std::fs::File;
5use std::io::prelude::*;
6use std::io::BufReader;
7
8use rayon::prelude::*;
9
10#[derive(Serialize)]
12pub struct FileStats {
13 pub lines: Option<usize>,
15 pub words: Option<usize>,
17 pub chars: Option<usize>,
19}
20
21impl fmt::Display for FileStats {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 write!(
27 f,
28 "{}{}{}",
29 match self.lines {
30 Some(lines) => format!("{} ", lines),
31 None => String::new(),
32 },
33 match self.words {
34 Some(words) => format!("{} ", words),
35 None => String::new(),
36 },
37 match self.chars {
38 Some(chars) => format!("{} ", chars),
39 None => String::new(),
40 },
41 )
42 }
43}
44
45#[derive(Copy, Clone, Debug)]
46pub struct AnalysisOptions {
47 pub lines: bool,
48 pub words: bool,
49 pub chars: bool,
50}
51
52#[derive(Serialize)]
53#[serde(untagged)]
54pub enum NamedOutput {
55 Success { filename: String, stats: FileStats },
56 Error { filename: String, error: String },
57}
58
59impl fmt::Display for NamedOutput {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 match self {
63 NamedOutput::Success { filename, stats } => write!(f, "{}{}", stats, filename),
65 NamedOutput::Error { filename, error } => write!(f, "{} {}", error, filename),
66 }
67 }
68}
69
70pub fn analyse_string(contents: &str, options: AnalysisOptions) -> FileStats {
72 FileStats {
73 lines: if options.lines {
74 Some(contents.lines().count())
75 } else {
76 None
77 },
78 words: if options.words {
79 Some(contents.split_whitespace().count())
80 } else {
81 None
82 },
83 chars: if options.chars {
84 Some(contents.chars().count())
85 } else {
86 None
87 },
88 }
89}
90
91pub fn analyse_file(filename: &str, options: AnalysisOptions) -> NamedOutput {
95 let file = match File::open(filename) {
96 Err(e) => {
97 return NamedOutput::Error {
98 filename: filename.to_string(),
99 error: e.to_string(),
100 }
101 }
102 Ok(f) => f,
103 };
104
105 let mut buf_reader = BufReader::new(file);
106 let mut contents = String::new();
107 match buf_reader.read_to_string(&mut contents) {
108 Ok(_bytes) => NamedOutput::Success {
109 filename: filename.to_string(),
110 stats: analyse_string(&contents, options),
111 },
112 Err(e) => NamedOutput::Error {
113 filename: filename.to_string(),
114 error: e.to_string(),
115 },
116 }
117}
118
119pub fn analyse_files(filenames: &[&str], options: AnalysisOptions) -> Vec<NamedOutput> {
123 filenames
124 .par_iter()
125 .map(|filename| analyse_file(filename, options))
126 .collect()
127}