-
Notifications
You must be signed in to change notification settings - Fork 0
/
utility.rs
153 lines (141 loc) · 4.96 KB
/
utility.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use std::collections::BTreeSet as Set;
use std::fs::File;
use std::io::Read;
use super::Tag;
use glob::{glob, PatternError};
const HEIRARCHY_SPLITTERS: [char; 2] = [':', '/'];
/// Get all files from either a passed path or under the current directory.
///
/// This will do a recursive glob for `.txt` and `.md` files. If the `root`
/// argument is `None`, then the current directory will be used; otherwise,
/// the given path will be used.
pub fn get_files(root: Option<String>) -> Result<Vec<String>, PatternError> {
let dir = match root {
Some(d) => d,
None => ".".to_string(),
};
let mut files = Vec::new();
let txts = glob(&format!("{}/**/*.txt", dir))?;
let mds = glob(&format!("{}/**/*.md", dir))?;
let orgs = glob(&format!("{}/**/*.org", dir))?;
for filename in txts.chain(mds).chain(orgs).flatten() {
files.push(filename.to_string_lossy().into());
}
Ok(files)
}
/// Get all tags for a single file
///
/// This will take all 'keywords' that match from a file, where a keyword
/// is defined as `@[a-zA-Z0-9_\-]`, i.e. any alphanumeric character, `_`,
/// or `-`. The keyword must be separate from it's surroundings (e.g. `\b`
/// in regex terminology)...spaces, start or end of line, punctuation all
/// count as being a 'boundary'. The leading `@` will be stripped.
pub fn get_tags_for_file(filename: &str) -> Set<Tag> {
let mut file =
File::open(filename).unwrap_or_else(|_| panic!("Couldn't open file: `{:?}`", filename));
let mut contents = String::new();
file.read_to_string(&mut contents)
.unwrap_or_else(|_| panic!("Couldn't read contents of file: `{:?}`", filename));
get_tags_from_string(&contents)
}
fn is_valid_tag_char(ch: char) -> bool {
ch.is_alphanumeric() || "-/:_".contains(ch)
}
pub fn get_tags_from_string(contents: &str) -> Set<Tag> {
let mut keywords = Set::new();
for line in contents.lines() {
for word in line.split_whitespace() {
if !word.starts_with('@') {
continue;
}
let mut is_valid = true;
for ch in word[1..].chars() {
if !is_valid_tag_char(ch) {
is_valid = false;
break;
}
}
if is_valid && !word[1..].is_empty() {
keywords.insert(parse_heirarchical_tag(&word[1..]));
}
}
}
keywords
}
pub fn display_as_tree(heirarchy: &[Tag]) -> String {
let mut heirarchy: Vec<Tag> = heirarchy.to_vec();
heirarchy.sort();
let mut path: Vec<String> = vec![];
let mut output = String::new();
for tagset in heirarchy {
// println!("NEW HEIRARCHY {:?}", tagset);
for (i, tag) in tagset.iter().enumerate() {
if let Some(t) = path.get(i) {
if t == tag {
continue;
} else {
while path.len() > i {
// println!("UNWINDING {:?}", path);
path.pop();
}
path.push(tag.to_string());
}
} else {
path.push(tag.to_string());
}
let indents = " ".repeat(path.len() - 1);
output.push_str(&format!("{}{}\n", indents, path[path.len() - 1]));
}
}
output
}
fn parse_heirarchical_tag(s: &str) -> Vec<String> {
s.trim_start_matches('@')
.split(|c: char| HEIRARCHY_SPLITTERS.contains(&c))
.map(|x| x.to_string())
.collect::<Vec<String>>()
}
#[allow(unused_imports)]
mod tests {
use super::*;
use std::collections::BTreeSet as Set;
#[test]
fn test_tags_from_string() {
let output = vec![vec!["a"], vec!["b"], vec!["c"], vec!["d", "e", "f"]]
.iter()
.cloned()
.map(|v| v.iter().map(|x| x.to_string()).collect())
.collect::<Set<Vec<String>>>();
let input = "@a @b @c @d/e/f";
assert_eq!(get_tags_from_string(input), output);
}
#[test]
fn display_as_tree_test() {
let output2 = String::from("completely\n unrelated\n heirarchy\nphilosophy\n mindset\n stoicism\n quote\n");
let input2 = vec![
vec![
"philosophy".to_string(),
"stoicism".to_string(),
"quote".to_string(),
],
vec!["philosophy".to_string(), "mindset".to_string()],
vec![
"completely".to_string(),
"unrelated".to_string(),
"heirarchy".to_string(),
],
];
assert_eq!(display_as_tree(&input2), output2);
}
#[test]
fn test_parse_heirarchical_tag() {
let tests = vec![
("@d/e/f", vec!["d", "e", "f"]),
("@delta/gamma", vec!["delta", "gamma"]),
("@single", vec!["single"]),
];
for (inp, outp) in tests {
assert_eq!(parse_heirarchical_tag(inp), outp);
}
}
}