diff --git a/Cargo.lock b/Cargo.lock index 66306a5..d989d5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,7 +81,6 @@ dependencies = [ "glob", "regex", "string-parser", - "string_format", "walkdir", ] @@ -291,12 +290,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46e079466fad2cd4673f6ccd80b3a2c8aa94e981a7ab7b4e5c023a0a37e23596" -[[package]] -name = "string_format" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a0f5f60043c6e865cc477a5cd3981df2210ec02592bd9ba18d32bcd72abb4eb" - [[package]] name = "strsim" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 551a7bc..9107dc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,5 @@ colored = "1.9.3" string-parser= "0.1.5" regex = "1.3.9" dirs = "2.0.2" -string_format = "0.1.0" clap = "2.33.1" -chrono = "0.4.13" \ No newline at end of file +chrono = "0.4.13" diff --git a/README.md b/README.md index 42a8695..5a55b0e 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,15 @@ > Fork from: https://github.com/ProbablyClem/cargo-todo +> todo comment format: +> #1 - priority +> !member - member +> @2020-01-01 - deadline (format: yyyy/MM/dd or yyyy-MM-dd) +> +> e.g. +> // todo #1 @2020-01-01 !hatter fix it + + ## Installation ``` $ cargo install cargo-todo diff --git a/src/main.rs b/src/main.rs index c6a8ee0..e9aa915 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,7 @@ -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::io::{ BufRead, BufReader }; use std::collections::HashSet; use glob::glob; use colored::Colorize; @@ -18,39 +16,42 @@ use crate::parser::*; use crate::regex::regex_parser; use crate::token::Token; +const BUILD_VER : &str = env!("CARGO_PKG_VERSION"); +const GIT_HASH : &str = env!("GIT_HASH"); +const BUILD_DATE: &str = env!("BUILD_DATE"); + 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("inline") .short("i").long("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", "p", "d", "m"]).help("Sort todos")) - .arg(Arg::with_name("member").short("m").long("member").takes_value(true).multiple(true).min_values(1).help("Filter from member")) + .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", "p", "d", "m"]).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") { + if matches.is_present("verbose") { + println!("{}\n", include_str!("logo.txt").green().bold()); + println!("Version : {}", BUILD_VER.underline()); + println!("Git hash : {}", GIT_HASH.underline()); + println!("Build date: {}", BUILD_DATE.underline()); + println!(); + } + + if matches.subcommand_matches("legacy").is_some() { main_legacy() } else { - let mut tokens : Vec = vec![]; - - let mut togo_config_path = String::from(dirs::home_dir().unwrap().to_str().unwrap()); - togo_config_path.push_str("/.cargo/todo_config"); - let regex = read_lines_to_vec(&togo_config_path); + let mut tokens: Vec = vec![]; + let regex = read_todo_config(); 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() - } - } { + for entry in glob(&all_rs_path).unwrap_or_else(|e| panic!("Couldn't access files. Error {}", e)) { let path = entry.unwrap(); let path = Path::new(&path).strip_prefix(env::current_dir().unwrap().to_str().unwrap()).unwrap(); // println!("{}", path.to_str().unwrap()); @@ -68,53 +69,50 @@ fn main() -> std::io::Result<()> { if let Some(sort) = matches.value_of("sort") { match sort { - "p" | "priority" => tokens.sort_unstable_by_key(|t : &Token| -> String { + "p" | "priority" => tokens.sort_unstable_by_key(|t: &Token| -> String { t.priority.clone().unwrap_or_else(|| "z".into()) }), - "d" | "deadline" => tokens.sort_unstable_by_key(|t : &Token| -> NaiveDate { + "d" | "deadline" => tokens.sort_unstable_by_key(|t: &Token| -> NaiveDate { t.date.clone().unwrap_or_else(|| NaiveDate::from_ymd(9999, 1, 1)) }), - "m" | "member" => tokens.sort_unstable_by_key(|t : &Token| -> String { + "m" | "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() + tokens = tokens.into_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() + tokens = tokens.into_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() + tokens = tokens.into_iter() .filter(|t| !excludes.contains(t.keyword.as_str())) - .map(|t| t.clone()) .collect::>(); } + if let Some(list) = matches.value_of("list") { + let head_count = match list.parse::() { + Ok(head_count) => head_count, Err(_) => { + eprintln!("{}", "list argument should be a valid number!".red()); + panic!() + } + }; + tokens = tokens.into_iter().take(head_count).collect::>(); + } + if matches.is_present("inline") { tokens.iter().for_each(|i| i.inline()); } else { @@ -125,47 +123,38 @@ fn main() -> std::io::Result<()> { } } -fn read_lines_to_vec(file_name: &str) -> Vec { - let mut regex = vec![]; - for line in read_lines(file_name).unwrap() { - let line = line.unwrap(); - if !line.is_empty() { regex.push(line) }; +fn read_todo_config() -> Vec { + let mut togo_config_path = String::from(dirs::home_dir().unwrap().to_str().unwrap()); + togo_config_path.push_str("/.cargo/todo_config"); + let home = dirs::home_dir().unwrap(); + let home_todo_config = home.join(".cargo").join("todo_config"); + if !home_todo_config.exists() { + return vec![ + r"^\s*//\s*todo\b".into(), + r"^\s*//\s*fix\b".into(), + r"^\s*//\s*fixme\b".into(), + ]; } - return regex; -} - -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()) + BufReader::new(File::open(home_todo_config).unwrap()).lines().map(|ln| ln.unwrap()).collect() } 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'))); + 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| { + 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()); + 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| { + 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)); @@ -173,13 +162,7 @@ fn main_legacy() -> std::io::Result<()> { 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() - } - } { + for entry in glob(&path).unwrap_or_else(|e| panic!("Couldn't access files. Error {}", e)) { let path = entry.unwrap(); let path = Path::new(&path).strip_prefix(env::current_dir().unwrap().to_str().unwrap()).unwrap(); if !path.starts_with("target/") { @@ -200,10 +183,12 @@ fn main_legacy() -> std::io::Result<()> { //todo implement @2001/11/01 #3 getters !clement //todo implement @2001-11-01 #3 getters !thomas //fix implement @18/11/2001 getters !jht5945 +// todo this is hatter sample test +// todo //4 //10/10/10 fn test(){ - todo!("implements getters"); + todo!("implements getters XXX !!!"); } //todo implement @2020/08/14 #5 getters !clement \ No newline at end of file diff --git a/src/parser.rs b/src/parser.rs index 8eb7f5b..181834d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -13,7 +13,7 @@ impl Parser { // let path = Path::new(file).strip_prefix(env::current_dir().unwrap().to_str().unwrap()).unwrap(); println!("{} {} {} {} : {}", file, "TODO".green() ,"Line ".green(), line.to_string().green(), text.blue()); }); - Parser{ keyword, end_filter, callback } + Parser { keyword, end_filter, callback } } pub fn new_callback(keyword: String, end_filter: Box) -> bool>, callback: Box) -> Parser { diff --git a/src/regex.rs b/src/regex.rs index f02211c..7a52b5f 100644 --- a/src/regex.rs +++ b/src/regex.rs @@ -6,18 +6,20 @@ use crate::token::*; // The output is wrapped in a Result to allow matching on errors // Returns an Iterator to the Reader of the lines of the file. -fn read_lines

(filename: P) -> io::Result>> where P: AsRef, { +fn read_lines

(filename: P) -> io::Result>> where P: AsRef { let file = File::open(filename)?; Ok(BufReader::new(file).lines()) } pub fn regex_parser(path: &str, regex: &[String], verbosity: i8) -> Result, io::Error> { - let set = RegexSet::new(regex).unwrap(); + let set = RegexSet::new(regex).unwrap_or_else(|e| panic!("Parse regex set: {:?}, failed: {}", regex, e)); + // println!("+++ {:?}", set); let mut tokens = vec![]; for (line_cpt, line) in (read_lines(path)?).enumerate() { let line_cpt_from1 = line_cpt + 1; let line = line.unwrap(); if set.is_match(line.to_lowercase().as_str()) { + // println!("+++ {}", line); tokens.push(Token::new(path.to_string(), line_cpt_from1, line, verbosity)); // println!("{}", t); } diff --git a/src/token.rs b/src/token.rs index cbc6d5f..fd3db0b 100644 --- a/src/token.rs +++ b/src/token.rs @@ -15,13 +15,21 @@ pub struct Token{ verbosity: i8, } +// priority, format #1 +const NUMBER_REGEX: &str = r"#[0-9]"; +// deadline, format @2020-01-01 +const DATE_REGEX: &str = r"@(\d{4})[/\-](\d{2})[/\-](\d{2})"; +// member, format !hatter +const MEMBER_REGEX: &str = r"![\w]+"; + impl Token { pub fn new(file: String, line: usize, s: String, verbosity: i8) -> Token { // println!(">>>>>>>>>>>{}", s); - let fields: Vec<&str>= s.split_whitespace().collect(); - let number_regex = Regex::new(r"#[0-9]").unwrap(); - let date_regex = Regex::new(r"@(\d{4})[/\-](\d{2})[/\-](\d{2})").unwrap(); - let member_regex = Regex::new(r"![\w]+").unwrap(); + let s = s.trim().chars().skip(2).collect::(); // remove leading "//" + let fields = s.trim().split_whitespace().collect::>(); + let number_regex = Regex::new(NUMBER_REGEX).unwrap(); + let date_regex = Regex::new(DATE_REGEX).unwrap(); + let member_regex = Regex::new(MEMBER_REGEX).unwrap(); // println!("///////////{:?}", fields); let mut t = Token { @@ -58,7 +66,7 @@ impl Token { pub fn inline(&self) { let mut inline_msg = vec![ - format!("{} line: {:>4} {:<6} ", self.file, self.line.to_string().green(), self.keyword.clone().green()) + format!("{} line: {} {} ", self.file, self.line.to_string().green(), self.keyword.green().bold()) ]; if let Some(member) = &self.member { inline_msg.push(format!("member: {}", member.red())); @@ -67,6 +75,7 @@ impl Token { inline_msg.push(format!("priority: {}", priority.red())); } if let Some(date) = &self.date { + // TODO display deadline vs current time inline !hatter inline_msg.push(format!("deadline: {}", date.to_string().red())); } if let Some(comment) = &self.comment { @@ -94,6 +103,7 @@ impl fmt::Display for Token { multiline_msg.push(format!("- priority: {}", priority.red())); } if let Some(date) = &self.date { + // TODO display deadline vs current time !hatter multiline_msg.push(format!("- deadline: {}", date.to_string().red())); } if let Some(comment) = &self.comment { @@ -101,7 +111,7 @@ impl fmt::Display for Token { } } - write!(f, "{}\n", multiline_msg.join("\n"))?; + writeln!(f, "{}", multiline_msg.join("\n"))?; Ok(()) } }