204 lines
8.2 KiB
Rust
204 lines
8.2 KiB
Rust
use std::io::Write;
|
|
use std::fs::OpenOptions;
|
|
use std::env;
|
|
use std::path::Path;
|
|
use std::fs::File;
|
|
use std::io::{ self, Lines, BufRead, BufReader };
|
|
use std::collections::HashSet;
|
|
use glob::glob;
|
|
use colored::Colorize;
|
|
use clap::{ Arg, App, SubCommand };
|
|
use chrono::NaiveDate;
|
|
|
|
mod parser;
|
|
mod regex;
|
|
mod token;
|
|
|
|
use crate::parser::*;
|
|
use crate::regex::regex_parser;
|
|
use crate::token::Token;
|
|
|
|
fn main() -> std::io::Result<()> {
|
|
let matches = App::new("Cargo-todo")
|
|
.author("Clément Guiton <clement.guiton.dev@gmail.com>")
|
|
.about("cargo tool to find TODOs in your code")
|
|
.arg(Arg::with_name("inline").short("i").long("inline").value_name("inline").takes_value(false).help("display todos in one line"))
|
|
.arg(Arg::with_name("filter").short("f").long("filter").takes_value(true).multiple(true).min_values(1).help("Filter todos to show"))
|
|
.arg(Arg::with_name("verbose").short("v").long("verbose").multiple(true).help("Sets the level of verbosity"))
|
|
.arg(Arg::with_name("exclude").short("x").long("exclude").takes_value(true).multiple(true).help("Exclude some todos from the list"))
|
|
.arg(Arg::with_name("list").short("l").long("list").takes_value(true).help("Number of values to display"))
|
|
.arg(Arg::with_name("sort").short("s").long("sort").takes_value(true).possible_values(&["priority", "deadline", "member"]).help("Sort todos"))
|
|
.arg(Arg::with_name("member").short("m").long("member").takes_value(true).multiple(true).min_values(1).help("Filter from member"))
|
|
.subcommand(SubCommand::with_name("legacy").about("Launch program in legacy mode (supports todo!(), etc..."))
|
|
.get_matches();
|
|
|
|
if let Some(_) = matches.subcommand_matches("legacy") {
|
|
main_legacy()
|
|
} else {
|
|
let mut tokens : Vec<Token> = vec![];
|
|
let mut togo_config_path = String::from(dirs::home_dir().unwrap().to_str().unwrap());
|
|
togo_config_path.push_str("/.cargo/todo_config");
|
|
|
|
let mut regex = vec![];
|
|
for line in read_lines(togo_config_path).unwrap() {
|
|
let line = line.unwrap();
|
|
if !line.is_empty() { regex.push(line) };
|
|
}
|
|
|
|
let mut all_rs_path = String::from(env::current_dir().unwrap().to_str().unwrap());
|
|
all_rs_path.push_str("/**/*.rs");
|
|
|
|
for entry in match glob(&all_rs_path) {
|
|
Ok(entry) => entry,
|
|
Err(e) => {
|
|
println!("Couldn't access files. Error {}", e);
|
|
Err(e).unwrap()
|
|
}
|
|
} {
|
|
let path = entry.unwrap();
|
|
let path = Path::new(&path).strip_prefix(env::current_dir().unwrap().to_str().unwrap()).unwrap();
|
|
// println!("{}", path.to_str().unwrap());
|
|
if !path.starts_with("target/") {
|
|
let file_path = path.to_str().unwrap();
|
|
|
|
let verbose_count = matches.occurrences_of("verbose");
|
|
let verbosity = if verbose_count == 0 || verbose_count == 2 { 2 } else { 1 };
|
|
match regex_parser(file_path, ®ex, verbosity) {
|
|
Ok(mut t) => tokens.append(&mut t),
|
|
Err(e) => eprintln!{"{}", e},
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(sort) = matches.value_of("sort") {
|
|
match sort {
|
|
"priority" => tokens.sort_unstable_by_key(|t : &Token| -> String {
|
|
t.priority.clone().unwrap_or_else(|| "z".into())
|
|
}),
|
|
"deadline" => tokens.sort_unstable_by_key(|t : &Token| -> NaiveDate {
|
|
t.date.clone().unwrap_or_else(|| NaiveDate::from_ymd(9999, 1, 1))
|
|
}),
|
|
"member" => tokens.sort_unstable_by_key(|t : &Token| -> String {
|
|
t.member.clone().unwrap_or_else(|| "zzzzzzzzzzzzz".into())
|
|
}),
|
|
_ => {}, // IGNORE
|
|
}
|
|
}
|
|
|
|
if let Some(list) = matches.value_of("list") {
|
|
let lines = match list.parse::<usize>() {
|
|
Ok(lines) => lines, Err(_) => {
|
|
eprintln!("{}", "list argument should be a valid number!".red());
|
|
panic!()
|
|
}
|
|
};
|
|
tokens = tokens.iter().take(lines).map(|t| t.clone()).collect::<Vec<Token>>();
|
|
}
|
|
|
|
if let Some(members) = matches.values_of("member") {
|
|
let members = members.collect::<HashSet<&str>>();
|
|
tokens = tokens.iter()
|
|
.filter(|t| t.member.as_ref().map(|m| members.contains(m.as_str())).unwrap_or(false))
|
|
.map(|t| t.clone())
|
|
.collect::<Vec<Token>>();
|
|
}
|
|
|
|
if let Some(filters) = matches.values_of("filter") {
|
|
let filters = filters.collect::<HashSet<&str>>();
|
|
tokens = tokens.iter()
|
|
.filter(|t| filters.contains(t.keyword.as_str()))
|
|
.map(|t| t.clone())
|
|
.collect::<Vec<Token>>();
|
|
}
|
|
|
|
if let Some(excludes) = matches.values_of("exclude") {
|
|
let excludes = excludes.collect::<HashSet<&str>>();
|
|
tokens = tokens.iter()
|
|
.filter(|t| !excludes.contains(t.keyword.as_str()))
|
|
.map(|t| t.clone())
|
|
.collect::<Vec<Token>>();
|
|
}
|
|
|
|
if matches.is_present("inline") {
|
|
tokens.iter().for_each(|i| i.inline());
|
|
} else {
|
|
tokens.iter().for_each(|i| println!("{}", i));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn read_lines<P>(filename: P) -> io::Result<Lines<BufReader<File>>> where P: AsRef<Path> {
|
|
let file = match File::open(&filename) {
|
|
Ok(line) => line,
|
|
Err(_) => {
|
|
println!("{}", "File '~/.cargo/todo_config' not found, creating it".red());
|
|
let mut f = OpenOptions::new().write(true).read(true).create(true).open(&filename).unwrap();
|
|
f.write_all(b"^s*//s*todo\\b\n").unwrap();
|
|
f.write_all(b"^s*//s*fix\\b\n").unwrap();
|
|
f.write_all(b"^s*//s*fixme\\b\n").unwrap();
|
|
return read_lines(filename);
|
|
}
|
|
};
|
|
Ok(BufReader::new(file).lines())
|
|
}
|
|
|
|
fn main_legacy() -> std::io::Result<()> {
|
|
let mut parsers : Vec<Parser> = vec![];
|
|
|
|
let mut path = String::from(env::current_dir().unwrap().to_str().unwrap());
|
|
path.push_str("/**/*.rs");
|
|
|
|
//we add a parser looking for the //todo keyword
|
|
parsers.push(Parser::new(String::from("//todo"), Box::from(|x : Vec<char>| x.last().unwrap() == &'\n')));
|
|
//we add a parser looking for the todo!() token
|
|
let todo_macro_callback = Box::from(|mut text : String, line : usize, file : &str| {
|
|
text.retain(|c| c != '\"');
|
|
println!("{} {} {} {} : {}",file,"TODO".green() ,"Line ".green(), line.to_string().green(), text.blue());
|
|
});
|
|
parsers.push(Parser::new_callback(String::from("todo!("), Box::from(|x : Vec<char>| x.last().unwrap() == &')'), todo_macro_callback));
|
|
|
|
//support for unimplemented
|
|
let unimplemented_macro_callback = Box::from(|text : String, line : usize, file : &str| {
|
|
println!("{} {} {} {} : {}{}{} ", file, "TODO".green(), "Line ".green(), line.to_string().green(), "unimplemented!(".blue(), text.magenta(), ")".blue());
|
|
});
|
|
parsers.push(Parser::new_callback(String::from("unimplemented!("), Box::from(|x : Vec<char>| x.last().unwrap() == &')'), unimplemented_macro_callback));
|
|
|
|
parsers.push(Parser::new(String::from("//fix"), Box::from(|x : Vec<char>| x.last().unwrap() == &'\n')));
|
|
|
|
//loop on every file within the current dir
|
|
for entry in match glob(&path) {
|
|
Ok(entry) => entry,
|
|
Err(e) => {
|
|
println!("Couldn't access files. Error {}", e);
|
|
Err(e).unwrap()
|
|
}
|
|
} {
|
|
let path = entry.unwrap();
|
|
let path = Path::new(&path).strip_prefix(env::current_dir().unwrap().to_str().unwrap()).unwrap();
|
|
if !path.starts_with("target/") {
|
|
let path = path.to_str().unwrap();
|
|
//execute each parsers on the current file
|
|
for p in &parsers {
|
|
p.parse(path);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
// test zone
|
|
//TODO refactor
|
|
//todo implement @2001/11/01 #3 getters !clement
|
|
//todo implement @2001-11-01 #3 getters !thomas
|
|
//fix implement @18/11/2001 getters !jht5945
|
|
//4
|
|
//10/10/10
|
|
fn test(){
|
|
todo!("implements getters");
|
|
}
|
|
|
|
//todo implement @2020/08/14 #5 getters !clement
|