Files
cargo-todo/src/main.rs
2020-07-26 17:35:40 +08:00

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, &regex, 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