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 ") .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 = Vec::new(); 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::() { Ok(lines) => lines, Err(_) => { eprintln!("{}", "list argument should be a valid number!".red()); panic!() } }; tokens = tokens.iter().take(lines).map(|t| t.clone()).collect::>(); } if let Some(members) = matches.values_of("member") { let members = members.collect::>(); tokens = tokens.iter() .filter(|t| t.member.as_ref().map(|m| members.contains(m.as_str())).unwrap_or(false)) .map(|t| t.clone()) .collect::>(); } if let Some(filters) = matches.values_of("filter") { let filters = filters.collect::>(); tokens = tokens.iter() .filter(|t| filters.contains(t.keyword.as_str())) .map(|t| t.clone()) .collect::>(); } if let Some(excludes) = matches.values_of("exclude") { let excludes = excludes.collect::>(); tokens = tokens.iter() .filter(|t| !excludes.contains(t.keyword.as_str())) .map(|t| t.clone()) .collect::>(); } if matches.is_present("inline") { tokens.iter().for_each(|i| i.inline()); } else { tokens.iter().for_each(|i| println!("{}", i)); } Ok(()) } } fn read_lines

(filename: P) -> io::Result>> where P: AsRef { 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 = 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| 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| 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| x.last().unwrap() == &')'), unimplemented_macro_callback)); parsers.push(Parser::new(String::from("//fix"), Box::from(|x : Vec| 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