Skip to content

Commit 234f444

Browse files
authored
Merge pull request #3 from EduardoLR10/rewrite-parser
Rewrite parser
2 parents 3b47334 + d897afa commit 234f444

File tree

5 files changed

+251
-64
lines changed

5 files changed

+251
-64
lines changed

src/cli.rs

-1
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,4 @@ pub enum RodoCommands {
1414
Catalog { opt_filepath: Option<String> },
1515
/// List all the TODOs in a given folder. The default value is the current directory.
1616
List { opt_filepath: Option<String> },
17-
1817
}

src/commands/list.rs

+31-39
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,39 @@
1-
use crate::commands::parser;
21
extern crate walkdir;
3-
use walkdir::{DirEntry, WalkDir};
42
use std::fs::File;
5-
use std::io::{ self, BufRead};
3+
use std::io::{self, Read};
64
use std::str;
5+
use walkdir::{DirEntry, WalkDir};
6+
7+
use super::parser::{parse_file, Todo};
78

89
fn is_hidden(entry: &DirEntry) -> bool {
9-
entry.file_name()
10-
.to_str()
11-
.map(|s| s.starts_with('.'))
12-
.unwrap_or(false)
10+
entry
11+
.file_name()
12+
.to_str()
13+
.map(|s| s.starts_with('.'))
14+
.unwrap_or(false)
1315
}
1416

15-
pub fn list_path(directory_path: &str) -> Result<Vec<String>, ()> {
16-
let mut todos = Vec::new();
17-
for file_path in WalkDir::new(directory_path).into_iter().filter_entry(|e| !is_hidden(e)).filter_map(|file| file.ok()) {
18-
if file_path.metadata().unwrap().is_file() {
19-
let file = File::open(file_path.path()).unwrap();
20-
let mut file_buffer = io::BufReader::new(file);
21-
let mut content_buffer = String::new();
22-
loop {
23-
match file_buffer.read_line(&mut content_buffer) {
24-
Ok(0) => break,
25-
Ok(_) => {
26-
content_buffer.pop(); // This is to remove the new line at the end
27-
match parser::todo()(content_buffer.as_str().as_bytes()) {
28-
Ok(x) => {
29-
match str::from_utf8(x.1) {
30-
Ok(v) => {
31-
todos.push(v.to_string());
32-
},
33-
Err(_e) => (),
34-
};
35-
},
36-
Err(_e) => ()
37-
}
38-
content_buffer.clear();
39-
},
40-
Err(_) => ()
41-
}
42-
}
43-
// println!("Parsed TODOs from file {:?}", file_path.path().display());
44-
}
45-
}
46-
Ok(todos)
17+
pub fn list_path_todos(directory_path: &str) -> Vec<Todo> {
18+
let mut todos = Vec::new();
19+
20+
// recursively enumerate files
21+
for file_path in WalkDir::new(directory_path)
22+
.into_iter()
23+
.filter_entry(|e| !is_hidden(e))
24+
.filter_map(|file| file.ok())
25+
{
26+
let file = File::open(file_path.path()).unwrap();
27+
let mut file_buffer = io::BufReader::new(file);
28+
let mut content_buffer = String::new();
29+
30+
// file doesn't contain valid string (binary file): skip
31+
file_buffer.read_to_string(&mut content_buffer).ok();
32+
33+
// look for todos in file
34+
let mut file_todos = parse_file(content_buffer.as_str());
35+
36+
todos.append(&mut file_todos)
37+
}
38+
todos
4739
}

src/commands/parser.rs

+203-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,206 @@
1-
use nom::{*, bytes::complete::{take_until, take_till}, character::{is_newline}, error::*, sequence::*};
1+
use nom::{
2+
bytes::complete::{tag, take_until},
3+
character::complete::{newline, not_line_ending},
4+
combinator::opt,
5+
multi::many0,
6+
*,
7+
};
28

3-
pub fn todo() -> impl Fn(&[u8]) -> IResult<&[u8], &[u8], Error<&[u8]>> {
4-
move |i: &[u8]| {
5-
let header = take_until("TODO:");
6-
let content = take_till(is_newline);
7-
preceded(header, content)(i)
9+
// TODO: change String to &str in the Todo type \
10+
// Making this will break the list_todos_path() function because of ownership of
11+
// the file contents.
12+
#[derive(Debug, PartialEq, Clone)]
13+
pub struct Todo {
14+
pub tag: String,
15+
pub text: String,
16+
}
17+
18+
fn parse_todo<'a>(todo_tag: &'a str, input: &'a str) -> IResult<&'a str, Todo> {
19+
// remove everything prior to the tag
20+
let (input, _) = take_until(todo_tag)(input)?;
21+
22+
let (input, _) = tag(todo_tag)(input)?;
23+
let (input, text) = not_line_ending(input)?;
24+
25+
// discard optional line ending
26+
let (input, _) = opt(newline)(input)?;
27+
28+
Ok((
29+
input,
30+
Todo {
31+
tag: todo_tag.to_owned(),
32+
text: text.to_owned(),
33+
},
34+
))
35+
}
36+
37+
fn parse_todos(file_content: &str) -> IResult<&str, Vec<Todo>> {
38+
let line_parser = |file_content| parse_todo("TODO:", file_content);
39+
let (input, todos) = many0(line_parser)(file_content)?;
40+
Ok((input, todos))
41+
}
42+
43+
pub fn parse_file(file_content: &str) -> Vec<Todo> {
44+
match parse_todos(file_content) {
45+
Ok((_, todos)) => todos,
46+
Err(_) => vec![],
47+
}
48+
}
49+
50+
#[cfg(test)]
51+
mod test {
52+
use crate::commands::parser::{parse_todo, parse_todos, Todo};
53+
54+
#[test]
55+
fn todos() {
56+
let input = "line 1\n\
57+
TODO: todo1\n\
58+
line 2\n\
59+
line 3 -- TODO: todo2\n";
60+
61+
assert_eq!(
62+
parse_todos(input),
63+
Ok((
64+
"",
65+
vec![
66+
Todo {
67+
tag: "TODO:".to_owned(),
68+
text: " todo1".to_owned()
69+
},
70+
Todo {
71+
tag: "TODO:".to_owned(),
72+
text: " todo2".to_owned()
73+
},
74+
]
75+
))
76+
);
77+
78+
let input = "line 1\n";
79+
assert_eq!(parse_todos(input), Ok(("line 1\n", vec![])));
80+
81+
let input = "line1 TODO: todo1\n";
82+
assert_eq!(
83+
parse_todos(input),
84+
Ok((
85+
"",
86+
vec![Todo {
87+
tag: "TODO:".to_owned(),
88+
text: " todo1".to_owned()
89+
}]
90+
))
91+
);
92+
93+
let input = "line1\n\
94+
line2\n\
95+
line3\n\
96+
TODO: todo1\n\
97+
line4\n";
98+
assert_eq!(
99+
parse_todos(input),
100+
Ok((
101+
"line4\n",
102+
vec![Todo {
103+
tag: "TODO:".to_owned(),
104+
text: " todo1".to_owned()
105+
}]
106+
))
107+
);
108+
109+
let input = "line1\n\
110+
line2\n\
111+
line3\n\
112+
FIXME: todo1\n\
113+
line4\n";
114+
assert_eq!(parse_todos(input), Ok((input, vec![])));
115+
}
116+
117+
#[test]
118+
fn todo_clean() {
119+
let input = "TODO: test todo";
120+
assert_eq!(
121+
parse_todo("TODO:", input),
122+
Ok((
123+
"",
124+
Todo {
125+
tag: "TODO:".to_owned(),
126+
text: " test todo".to_owned()
127+
}
128+
))
129+
);
130+
}
131+
132+
#[test]
133+
fn todo_tag_only() {
134+
let input = "TODO:\nline1";
135+
assert_eq!(
136+
parse_todo("TODO:", input),
137+
Ok((
138+
"line1",
139+
Todo {
140+
tag: "TODO:".to_owned(),
141+
text: "".to_owned()
142+
}
143+
))
144+
);
145+
146+
let input = "TODO:";
147+
assert_eq!(
148+
parse_todo("TODO:", input),
149+
Ok((
150+
"",
151+
Todo {
152+
tag: "TODO:".to_owned(),
153+
text: "".to_owned()
154+
}
155+
))
156+
);
157+
158+
let input = "TODO:\n";
159+
assert_eq!(
160+
parse_todo("TODO:", input),
161+
Ok((
162+
"",
163+
Todo {
164+
tag: "TODO:".to_owned(),
165+
text: "".to_owned()
166+
}
167+
))
168+
);
169+
}
170+
171+
#[test]
172+
fn todo_clean_newline() {
173+
let input = "TODO: test todo\nline1";
174+
assert_eq!(
175+
parse_todo("TODO:", input),
176+
Ok((
177+
"line1",
178+
Todo {
179+
tag: "TODO:".to_owned(),
180+
text: " test todo".to_owned()
181+
}
182+
))
183+
);
184+
}
185+
186+
#[test]
187+
fn todo_in_comment() {
188+
let input = "// TODO: test todo";
189+
assert_eq!(
190+
parse_todo("TODO:", input),
191+
Ok((
192+
"",
193+
Todo {
194+
tag: "TODO:".to_owned(),
195+
text: " test todo".to_owned()
196+
}
197+
))
198+
);
199+
}
200+
201+
#[test]
202+
fn todo_different_tag() {
203+
let input = "FIXME: different tag";
204+
assert_eq!(parse_todo("TODO:", input).ok(), None);
8205
}
9206
}

src/display.rs

+9-10
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
use colored::Colorize;
22

3-
fn display_todo(msg: &mut String) {
4-
msg.replace_range(..5, "");
5-
let body = msg.trim();
6-
println!(
7-
"{} {}",
8-
"TODO:".bold().blue().underline(),
9-
body)
3+
use crate::commands::parser::Todo;
4+
5+
impl Todo {
6+
pub fn display(todo: Todo) -> String {
7+
format!("{} {}", todo.tag.bold().blue().underline(), todo.text)
8+
}
109
}
1110

12-
pub fn display_todos(msgs: Vec<String>) {
13-
for mut msg in msgs {
14-
display_todo(&mut msg);
11+
pub fn display_todos(todos: Vec<Todo>) {
12+
for todo in todos {
13+
println!("{}", Todo::display(todo));
1514
}
1615
}

src/main.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
mod cli;
2+
use clap::Parser;
23
use cli::Cli;
34
use cli::RodoCommands;
4-
use clap::{Parser};
55
mod commands;
66
use commands::catalog;
77
use commands::list;
@@ -15,13 +15,13 @@ fn main() {
1515
let current_dir_str = current_dir.into_os_string().into_string().unwrap();
1616
match &cli.command {
1717
RodoCommands::Catalog { opt_filepath } => {
18-
let filepath = opt_filepath.to_owned().unwrap_or(current_dir_str);
19-
let _todos = catalog::catalog_path(filepath.as_str()).unwrap();
20-
},
21-
RodoCommands::List { opt_filepath } => {
22-
let filepath = opt_filepath.to_owned().unwrap_or(current_dir_str);
23-
let todos = list::list_path(filepath.as_str()).unwrap();
24-
display_todos(todos)
18+
let filepath = opt_filepath.to_owned().unwrap_or(current_dir_str);
19+
let _todos = catalog::catalog_path(filepath.as_str()).unwrap();
20+
}
21+
RodoCommands::List { opt_filepath } => {
22+
let filepath = opt_filepath.to_owned().unwrap_or(current_dir_str);
23+
let todos = list::list_path_todos(filepath.as_str());
24+
display_todos(todos)
2525
}
2626
}
2727
}

0 commit comments

Comments
 (0)