git_scanner/
file_walker.rs1#![warn(clippy::all)]
2
3use super::flare;
4use super::flare::FlareTreeNode;
5use super::indicator_calculator::IndicatorCalculator;
6use failure::Error;
7use ignore::{Walk, WalkBuilder};
8#[allow(unused_imports)]
9use path_slash::PathExt;
10use std::path::Path;
11
12fn apply_calculators_to_node(
13 node: &mut FlareTreeNode,
14 path: &Path,
15 toxicity_indicator_calculators: &mut Vec<Box<dyn IndicatorCalculator>>,
16) {
17 toxicity_indicator_calculators.iter_mut().for_each(|tic| {
18 let indicators = tic.calculate(path);
19 match indicators {
20 Ok(Some(indicators)) => node.add_data(tic.name(), indicators),
21 Ok(None) => (),
22 Err(error) => {
23 warn!(
24 "Can't find {} indicators for {:?} - cause: {}",
25 tic.name(),
26 node,
27 error
28 );
29 }
30 }
31 });
32}
33
34fn walk_tree_walker(
35 walker: Walk,
36 prefix: &Path,
37 toxicity_indicator_calculators: &mut Vec<Box<dyn IndicatorCalculator>>,
38) -> Result<flare::FlareTreeNode, Error> {
39 let mut tree = FlareTreeNode::new(flare::ROOT_NAME, false);
40
41 apply_calculators_to_node(&mut tree, prefix, toxicity_indicator_calculators);
42
43 for result in walker.map(|r| r.expect("File error!")).skip(1) {
44 let p = result.path();
45 let relative = p.strip_prefix(prefix)?;
46 let new_child = if p.is_dir() || p.is_file() {
47 let mut f = FlareTreeNode::new(p.file_name().unwrap(), p.is_file());
48 apply_calculators_to_node(&mut f, p, toxicity_indicator_calculators);
49 Some(f)
50 } else {
51 warn!("Not a file or dir: {:?} - skipping", p);
52 None
53 };
54
55 if let Some(new_child) = new_child {
56 match relative.parent() {
57 Some(new_parent) => {
58 let parent = tree
59 .get_in_mut(&mut new_parent.components())
60 .expect("no parent found!");
61 parent.append_child(new_child);
62 }
63 None => {
64 tree.append_child(new_child);
65 }
66 }
67 }
68 }
69 Ok(tree)
70}
71
72pub fn walk_directory(
73 root: &Path,
74 toxicity_indicator_calculators: &mut Vec<Box<dyn IndicatorCalculator>>,
75) -> Result<flare::FlareTreeNode, Error> {
76 walk_tree_walker(
77 WalkBuilder::new(root)
78 .add_custom_ignore_filename(".polyglot_code_scanner_ignore")
79 .sort_by_file_name(|name1, name2| name1.cmp(name2))
80 .build(),
81 root,
82 toxicity_indicator_calculators,
83 )
84}
85
86#[cfg(test)]
87mod test {
88 use super::*;
89 use serde_json::{json, Value};
90 use test_shared::*;
91
92 #[test]
93 fn scanning_a_filesystem_builds_a_tree() {
94 let root = Path::new("./tests/data/simple/");
95 let tree = walk_directory(root, &mut Vec::new()).unwrap();
96
97 assert_eq_json_file(&tree, "./tests/expected/simple_files.json")
98 }
99
100 #[derive(Debug)]
101 struct SimpleTIC {}
102
103 impl IndicatorCalculator for SimpleTIC {
104 fn name(&self) -> String {
105 "foo".to_string()
106 }
107 fn calculate(&mut self, path: &Path) -> Result<Option<serde_json::Value>, Error> {
108 if path.is_file() {
109 Ok(Some(json!("bar")))
110 } else {
111 Ok(None)
112 }
113 }
114
115 fn metadata(&self) -> Result<Option<Value>, Error> {
116 unimplemented!()
117 }
118 }
119
120 #[derive(Debug)]
121 struct SelfNamingTIC {}
122
123 impl IndicatorCalculator for SelfNamingTIC {
124 fn name(&self) -> String {
125 "filename".to_string()
126 }
127 fn calculate(&mut self, path: &Path) -> Result<Option<serde_json::Value>, Error> {
128 if path.is_file() {
129 Ok(Some(json!(path.to_slash_lossy())))
130 } else {
131 Ok(None)
132 }
133 }
134
135 fn metadata(&self) -> Result<Option<Value>, Error> {
136 unimplemented!()
137 }
138 }
139
140 #[test]
141 fn scanning_merges_data_from_mutators() {
142 let root = Path::new("./tests/data/simple/");
143 let simple_tic = SimpleTIC {};
144 let self_naming_tic = SelfNamingTIC {};
145 let calculators: &mut Vec<Box<dyn IndicatorCalculator>> =
146 &mut vec![Box::new(simple_tic), Box::new(self_naming_tic)];
147
148 let tree = walk_directory(root, calculators).unwrap();
149
150 assert_eq_json_file(&tree, "./tests/expected/simple_files_with_data.json");
151 }
152
153 #[derive(Debug)]
154 struct MutableTIC {
155 count: i64,
156 }
157
158 impl IndicatorCalculator for MutableTIC {
159 fn name(&self) -> String {
160 "count".to_string()
161 }
162 fn calculate(&mut self, _path: &Path) -> Result<Option<serde_json::Value>, Error> {
163 let result = json!(self.count);
164 self.count += 1;
165 Ok(Some(result))
166 }
167
168 fn metadata(&self) -> Result<Option<Value>, Error> {
169 unimplemented!()
170 }
171 }
172
173 #[test]
174 fn can_mutate_state_of_calculator() {
175 let root = Path::new("./tests/data/simple/");
176 let tic = MutableTIC { count: 0 };
177 let calculators: &mut Vec<Box<dyn IndicatorCalculator>> = &mut vec![Box::new(tic)];
178
179 let tree = walk_directory(root, calculators).unwrap();
180
181 assert_eq_json_file(&tree, "./tests/expected/simple_files_with_counts.json");
182 }
183}