chore: first forked commit

This commit is contained in:
2020-07-26 14:34:11 +08:00
parent 0056812563
commit af156e7f7c
8 changed files with 1057 additions and 5 deletions

331
src/main.rs Normal file
View File

@@ -0,0 +1,331 @@
extern crate string_format;
extern crate clap;
extern crate walkdir;
extern crate string_parser;
extern crate dirs;
extern crate glob;
extern crate chrono;
use glob::glob;
use colored::Colorize;
use clap::{Arg, App, SubCommand};
use chrono::NaiveDate;
//local files
mod parser;
mod regex;
mod token;
use crate::parser::*;
use crate::regex::regex_parser;
use crate::token::Token;
//std
use std::io::Write;
use std::fs::OpenOptions;
use std::env;
use std::path::Path;
use std::fs::File;
use std::io::{self, BufRead};
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")
.help("display todos in one line")
.takes_value(false))
.arg(Arg::with_name("filter")
.help("Filter todos to show")
.short("f")
.long("filter")
.takes_value(true)
.multiple(true)
.min_values(1))
.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) = matches.subcommand_matches("legacy") {
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>| {if x.last().unwrap() == &'\n' {return true;} else { return false}})));
//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>| {if x.last().unwrap() == &')' {return true;} else { return false}}), _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>| {if x.last().unwrap() == &')' {return true;} else { return false}}), _unimplemented_macro_callback));
parsers.push(Parser::new(String::from("//fix"), Box::from(|x : Vec<char>| {if x.last().unwrap() == &'\n' {return true;} else { return false}})));
//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(())
}
else{
let mut tokens : Vec<Token> = Vec::new();
let mut path = String::from(dirs::home_dir().unwrap().to_str().unwrap());
path.push_str("/.cargo/todo_config");
// println!("{}",path);
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::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(io::BufReader::new(file).lines())
}
let mut regex = Vec::new();
for line in read_lines(path).unwrap() {
let line = line.unwrap();
regex.push(line);
}
let mut path = String::from(env::current_dir().unwrap().to_str().unwrap());
path.push_str("/**/*.rs");
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();
// println!("{}", path.to_str().unwrap());
if !path.starts_with("target/"){
let path = path.to_str().unwrap();
if matches.occurrences_of("verbose") == 0 || matches.occurrences_of("verbose") == 2{
match regex_parser(path, regex.clone(), 2){
Ok(mut t) => {
tokens.append(&mut t);
},
Err(e) => eprintln!{"{}", e},
}
}
else {
match regex_parser(path, regex.clone(), 1){
Ok(mut t) => {
tokens.append(&mut t);
},
Err(e) => eprintln!{"{}", e},
}
}
}
}
if matches.is_present("sort"){
if matches.value_of("sort").unwrap() == "priority"{
fn token_priority_sort(t : &Token) -> String {
if t.priority.is_none() {
return String::from("z");
}
else {
return t.priority.clone().unwrap()
}
}
tokens.sort_unstable_by_key(token_priority_sort);
}
else if matches.value_of("sort").unwrap() == "deadline"{
fn token_deadline_sort(t : &Token) -> NaiveDate {
if t.date.is_none() {
return NaiveDate::from_ymd(3000,01,01);
}
else {
return t.date.clone().unwrap()
}
}
tokens.sort_unstable_by_key(token_deadline_sort);
}
else if matches.value_of("sort").unwrap() == "member"{
fn token_member_sort(t : &Token) -> String {
if t.priority.is_none() {
return String::from("z");
}
else {
return t.priority.clone().unwrap()
}
}
tokens.sort_unstable_by_key(token_member_sort);
}
}
if matches.is_present("list"){
let lines = match matches.value_of("list").unwrap().parse::<usize>(){
Ok(lines) => lines,
Err(_) => {
eprintln!("{}", "list argument should be a valid number!".red());
panic!()
}};
let mut new_tokens : Vec<Token> = Vec::new();
for i in tokens{
if new_tokens.len() < lines{
&new_tokens.push(i.clone());
}
else
{
break;
}
}
tokens = new_tokens;
}
if matches.is_present("member"){
let filters : Vec<&str> = matches.values_of("member").unwrap().collect();
let mut new_tokens : Vec<Token> = Vec::new();
for i in tokens{
// println!("{}", i);
for y in &filters {
if i.member.clone().is_some() && i.member.clone().unwrap() == *y.to_string(){
println!("pushing");
&new_tokens.push(i.clone());
break;
}
}
}
tokens = new_tokens;
}
if matches.is_present("filter"){
let filters : Vec<&str> = matches.values_of("filter").unwrap().collect();
let mut new_tokens : Vec<Token> = Vec::new();
for i in tokens{
for y in &filters {
if i.keyword == String::from(*y){
&new_tokens.push(i.clone());
break;
}
}
}
tokens = new_tokens;
// tokens = new.into_iter().filter(|t| t.keyword == String::from(matches.value_of("filter").unwrap())).collect();
}
if matches.is_present("exclude"){
let excludes : Vec<&str> = matches.values_of("exclude").unwrap().collect();
let mut new_tokens : Vec<Token> = Vec::new();
for i in tokens{
for y in 0..excludes.len() {
if i.keyword == String::from(excludes[y]){
break;
}
else if y == excludes.len() -1{
&new_tokens.push(i.clone());
}
}
}
tokens = new_tokens;
// tokens = new.into_iter().filter(|t| t.keyword == String::from(matches.value_of("filter").unwrap())).collect();
}
if matches.is_present("inline"){
for i in tokens{
i.inline();
}
}
else {
for i in tokens {
println!("{}", i);
}
}
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
//4
//10/10/10
fn test(){
todo!("implements getters");
}
//todo implement 2020/08/14 5 getters !clement

40
src/parser.rs Normal file
View File

@@ -0,0 +1,40 @@
extern crate string_parser;
use string_parser::string_parser_with_file;
use colored::Colorize;
pub struct Parser{
keyword : String,
end_filter : Box<dyn Fn(Vec<char>) -> bool>,
callback : Box<dyn Fn(String, usize, &str)>,
}
impl Parser {
pub fn new(keyword : String, end_filter : Box<dyn Fn(Vec<char>) -> bool>) -> Parser{
let callback = Box::from(|text : String, line : usize, file : &str| {
// 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: keyword, end_filter : end_filter, callback}
}
pub fn new_callback(keyword : String, end_filter : Box<dyn Fn(Vec<char>) -> bool>, callback : Box<dyn Fn(String, usize, &str)>) -> Parser{
Parser{keyword: keyword, end_filter : end_filter, callback}
}
fn get_keyword(&self) -> String {
self.keyword.clone()
}
fn get_end_filter(&self) -> &Box<dyn Fn(Vec<char>) -> bool> {
&self.end_filter
}
fn get_callback(&self) -> &Box<dyn Fn(String, usize, &str)> {
&self.callback
}
pub fn parse(&self, path : &str) {
string_parser_with_file(path, self.get_keyword().as_str(), self.get_end_filter(), self.get_callback()).expect("failed to open file");
}
}

32
src/regex.rs Normal file
View File

@@ -0,0 +1,32 @@
extern crate regex;
use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;
use regex::{RegexSet};
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<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where P: AsRef<Path>, {
let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines())
}
pub fn regex_parser(path : &str, regex : Vec<String>, verbosity : i8) -> Result<Vec<Token>, io::Error>{
let set = RegexSet::new(regex).unwrap();
let mut tokens = Vec::new();
let mut line_cpt = 0;
for line in read_lines(path)? {
line_cpt +=1;
let line = line.unwrap();
if set.is_match(line.to_lowercase().as_str()){
tokens.push(Token::new(path.to_string(), line_cpt, line, verbosity));
// println!("{}", t);
}
}
Ok(tokens)
}

133
src/token.rs Normal file
View File

@@ -0,0 +1,133 @@
extern crate string_format;
extern crate regex;
extern crate chrono;
use chrono::NaiveDate;
use string_format::string_format;
use std::fmt;
use colored::Colorize;
use regex::Regex;
#[derive(Clone)]
pub struct Token{
file : String,
line : usize,
pub keyword : String,
pub comment : Option<String>,
pub priority : Option<String>,
pub date : Option<NaiveDate>,
pub member : Option<String>,
verbosity : i8,
}
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("\\b[1-9]\\b").unwrap();
let date_regex = Regex::new("(\\d*/\\d*/\\d*)").unwrap();
let member_regex = Regex::new("!\\w*").unwrap();
if date_regex.is_match("5") {
panic!("regex");
}
// for i in &fields {
// println!("{}", i);
// }
let mut t = Token {
file : file,
line : line,
keyword: "todo".to_string(),
comment : None,
priority : None,
date : None,
member : None,
verbosity : verbosity
};
for i in 0..fields.len() {
if i == 0{
t.keyword = fields[0].to_string().to_lowercase();
}
else if number_regex.is_match(fields[i]) {
t.priority = Some(fields[i].to_string());
}
else if date_regex.is_match(fields[i]){
let date : Vec<&str> = fields[i].split("/").collect();
t.date = NaiveDate::from_ymd_opt(date[0].parse::<i32>().unwrap(), date[1].parse::<u32>().unwrap(), date[2].parse::<u32>().unwrap());
// t.date = Some(fields[i].to_string());
}
else if member_regex.is_match(fields[i]){
let mut member = String::new(); //from(fields[i].clone()).chars().next().map(|c| &s[c.len_utf8()..]).unwrap();
let it = fields[i].chars().skip(1);
for i in it{
member.push(i);
}
t.member = Some(member);
}
else {
if t.comment.is_none(){
t.comment = Some(fields[i].to_string());
}
else{
t.comment = Some(string_format!("{} {}".to_string(),t.comment.unwrap(), fields[i].to_string()));
}
}
}
t
}
pub fn inline(&self) {
let mut s;
s = string_format!("{} line: {} {} ".to_string(), self.file.clone(), self.line.to_string().green().to_string(), self.keyword.clone().green().to_string());
if self.member.is_some(){
s = string_format!("{} Member: {}".to_string(),s ,self.member.clone().unwrap().red().to_string());
}
if self.priority.is_some(){
s = string_format!("{} Priority: {}".to_string(), s, self.priority.clone().unwrap().red().to_string());
}
if self.date.is_some(){
s = string_format!("{} Deadline: {}".to_string(), s, self.date.clone().unwrap().to_string().red().to_string());
}
if self.comment.is_some() {
s = string_format!("{} {}".to_string(), s, self.comment.clone().unwrap().blue().to_string());
}
println!("{}", s);
}
}
// To use the `{}` marker, the trait `fmt::Display` must be implemented
// manually for the type.
impl fmt::Display for Token {
// This trait requires `fmt` with this exact signature.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut s;
s = string_format!("{} line: {} {} \n".to_string(), self.file.clone(), self.line.to_string().green().to_string(), self.keyword.clone().green().to_string());
if self.verbosity <= 1{
if self.comment.is_some() {
s = string_format!("{}{}\n".to_string(), s, self.comment.clone().unwrap().blue().to_string());
}
}
else {
if self.member.is_some(){
s = string_format!("{}Member: {}\n".to_string(),s ,self.member.clone().unwrap().red().to_string());
}
if self.priority.is_some(){
s = string_format!("{}Priority: {}\n".to_string(), s, self.priority.clone().unwrap().red().to_string());
}
if self.date.is_some(){
s = string_format!("{}Deadline: {}\n".to_string(), s, self.date.clone().unwrap().to_string().red().to_string());
}
if self.comment.is_some() {
s = string_format!("{}{}\n".to_string(), s, self.comment.clone().unwrap().blue().to_string());
}
}
write!(f, "{}", s)?;
Ok(())
}
}