Skip to content

Commit

Permalink
feat: add todo filtering (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
believer authored Mar 24, 2023
1 parent b718cee commit 3335e61
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 91 deletions.
38 changes: 23 additions & 15 deletions .github/workflows/pr_check.yml
Original file line number Diff line number Diff line change
@@ -1,38 +1,46 @@
name: Release
name: PR check

on:
pull_request:
branches:
- main

env:
CARGO_TERM_COLOR: always
# Cancel previously running workflows
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.9.1

- name: Checkout code
uses: actions/checkout@v3

- name: Setup Rust (nightly)
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@nightly
id: rust-toolchain
with:
profile: minimal
toolchain: nightly
components: rustfmt, clippy
override: true

- name: Run linting
run: cargo clippy --all-targets --all-features -- -D warnings

- name: Run formatting
run: cargo fmt --all -- --check
- name: Cache cargo
uses: actions/cache@v3
id: cache-cargo
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }}

- name: Run tests
run: cargo test --locked

- name: Run linting
run: cargo clippy --all-targets --all-features -- -D warnings

- name: Check formatting
run: cargo fmt --all -- --check
61 changes: 34 additions & 27 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
mod models;

use colored::*;
use colored::Colorize;
use jwalk::WalkDir;
use models::{todo::Todo, todo_type::contains_todo_type};
use std::{collections::HashMap, fs};
use std::fs;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = std::env::args().nth(1);
let needle = std::env::args().nth(2);

// Display help if no path is provided
if path.is_none() {
println!("Usage: todo <path>");
return Ok(());
}

let path = path.unwrap();
let mut files_scanned = 0;
let mut ok_files = 0;
let mut todos: HashMap<String, Vec<Todo>> = HashMap::new();
let mut filtered_files = 0;
let mut todos: Vec<Todo> = vec![];

let path = path.unwrap();
let supported_filetypes = vec!["ts", "js", "tsx", "jsx", "vue", "html"];

for entry in WalkDir::new(&path).sort(true) {
let entry = entry?;
files_scanned += 1;

// Skip directories
if entry.file_type().is_dir() {
Expand All @@ -34,40 +40,41 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Read the file contents
let file_contents = fs::read_to_string(entry.path())?;

// Skip files without TODOs
if !contains_todo_type(&file_contents) {
// Skip files without comments
if !file_contents.contains("//") && !file_contents.contains("<!--") {
ok_files += 1;
continue;
}

// Check and parse todos
for line in file_contents.lines().enumerate() {
if contains_todo_type(line.1) {
todos
.entry(entry.path().display().to_string())
.or_default()
.push(line.into());
if contains_todo_type(line.1).is_ok() {
if needle.is_some() && !line.1.contains(needle.as_ref().unwrap()) {
filtered_files += 1;
continue;
}

let todo = (line.0, line.1, entry.path().display().to_string());

todos.push(todo.into());
}
}
}

// Print the results
todos.iter().for_each(|todo| println!("{todo}"));

println!(
"{ok_files} {} {}\n",
match ok_files {
1 => "file",
_ => "files",
},
"OK".green(),
"\n{} {}\n{} {} scanned {} {} OK {} {} filtered",
"Todos:".bold(),
todos.len().to_string().blue(),
"Files:".bold(),
files_scanned.to_string().yellow(),
"/".bright_black(),
ok_files.to_string().green(),
"/".bright_black(),
filtered_files.to_string().red(),
);

for file in todos {
for todo in file.1 {
println!(
"{} {}",
todo,
file.0.replace(&path, "").bold().bright_black(),
);
}
}

Ok(())
}
39 changes: 19 additions & 20 deletions src/models/todo.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,34 @@
use super::todo_type::TodoType;
use super::todo_type::{contains_todo_type, TodoType};
use colored::*;
use nom::{
branch,
bytes::complete::{tag, take_till1},
character::complete,
IResult,
};
use nom::{bytes::complete::take_till1, character::complete, IResult};
use std::fmt::Display;

pub struct Todo {
file_path: String,
line_number: (usize, usize),
text: String,
todo_type: TodoType,
}

fn parse_todo(input: &str, line_number: usize) -> IResult<&str, Todo> {
fn parse_todo(input: &str, line_number: usize, file_path: String) -> IResult<&str, Todo> {
let (input, column) = complete::multispace0(input)?;
let (input, _) = take_till1(char::is_alphabetic)(input)?;
let (input, todo_type) =
branch::alt((tag("TODO"), tag("FIX"), tag("WARNING"), tag("NOTE")))(input)?;
let (input, todo_type) = contains_todo_type(input)?;
let (input, _) = take_till1(char::is_alphabetic)(input)?;

Ok((
input,
Todo {
file_path,
line_number: (line_number + 1, column.len() + 1),
text: input.replace(" -->", "").to_string(),
text: input.replace("-->", "").trim().to_string(),
todo_type: todo_type.into(),
},
))
}

impl From<(usize, &str)> for Todo {
fn from((line_number, text): (usize, &str)) -> Self {
parse_todo(text, line_number).unwrap().1
impl From<(usize, &str, String)> for Todo {
fn from((line_number, text, path): (usize, &str, String)) -> Self {
parse_todo(text, line_number, path).unwrap().1
}
}

Expand All @@ -44,7 +39,11 @@ impl Display for Todo {
"{} {} {}",
self.todo_type,
self.text,
format!("[{}:{}]", self.line_number.0, self.line_number.1).bright_black()
format!(
"[{}:{}:{}]",
self.file_path, self.line_number.0, self.line_number.1
)
.bright_black()
)
}
}
Expand All @@ -55,7 +54,7 @@ mod tests {

#[test]
fn test_todo_from() {
let todo = Todo::from((0, " // TODO: This is a todo"));
let todo = Todo::from((0, " // TODO: This is a todo", "test.rs".to_string()));

assert_eq!(todo.line_number, (1, 3));
assert_eq!(todo.text, "This is a todo");
Expand All @@ -64,7 +63,7 @@ mod tests {

#[test]
fn test_todo_from_with_other_delimiter() {
let todo = Todo::from((0, " // TODO -> This is a todo"));
let todo = Todo::from((0, " // TODO -> This is a todo", "test.rs".to_string()));

assert_eq!(todo.line_number, (1, 3));
assert_eq!(todo.text, "This is a todo");
Expand All @@ -73,7 +72,7 @@ mod tests {

#[test]
fn test_todo_from_without_spacing() {
let todo = Todo::from((0, "//TODO: This is a todo"));
let todo = Todo::from((0, "//TODO: This is a todo", "test.rs".to_string()));

assert_eq!(todo.line_number, (1, 1));
assert_eq!(todo.text, "This is a todo");
Expand All @@ -82,7 +81,7 @@ mod tests {

#[test]
fn todo_from_removes_closing_tag() {
let todo = Todo::from((0, "<!-- TODO: This is a todo -->"));
let todo = Todo::from((0, "<!-- TODO: This is a todo -->", "test.rs".to_string()));

assert_eq!(todo.line_number, (1, 1));
assert_eq!(todo.text, "This is a todo");
Expand Down
54 changes: 25 additions & 29 deletions src/models/todo_type.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
use colored::*;
use nom::{
branch,
bytes::complete::{tag, take_till1},
IResult,
};
use std::fmt::Display;

#[derive(PartialEq, Debug)]
Expand Down Expand Up @@ -32,20 +37,11 @@ impl Display for TodoType {
}
}

pub fn contains_todo_type(text: &str) -> bool {
let valid_todo_types = vec!["TODO", "FIX", "WARNING", "NOTE"];
pub fn contains_todo_type(input: &str) -> IResult<&str, &str> {
let (input, _) = nom::character::complete::multispace0(input)?;
let (input, _) = take_till1(char::is_alphabetic)(input)?;

for todo_type in valid_todo_types {
if text.contains(&format!("// {}", todo_type))
|| text.contains(&format!("<!-- {}", todo_type))
|| text.contains(&format!("//{}", todo_type))
|| text.contains(&format!("<!--{}", todo_type))
{
return true;
}
}

false
branch::alt((tag("TODO"), tag("FIX"), tag("WARNING"), tag("NOTE")))(input)
}

#[cfg(test)]
Expand All @@ -55,30 +51,30 @@ mod tests {
#[test]
fn test_todo_types() {
// Handles JS/TS comments
assert!(contains_todo_type("// TODO: This is a todo"));
assert!(contains_todo_type("// FIX: This is a fix"));
assert!(contains_todo_type("// WARNING: This is a warning"));
assert!(contains_todo_type("// NOTE: This is a note"));
assert!(contains_todo_type("// TODO: This is a todo").is_ok());
assert!(contains_todo_type("// FIX: This is a fix").is_ok());
assert!(contains_todo_type("// WARNING: This is a warning").is_ok());
assert!(contains_todo_type("// NOTE: This is a note").is_ok());

// Handles HTML comments
assert!(contains_todo_type("<!-- TODO: This is a todo -->"));
assert!(contains_todo_type("<!-- FIX: This is a fix -->"));
assert!(contains_todo_type("<!-- WARNING: This is a warning -->"));
assert!(contains_todo_type("<!-- NOTE: This is a note -->"));
assert!(contains_todo_type("<!-- TODO: This is a todo -->").is_ok());
assert!(contains_todo_type("<!-- FIX: This is a fix -->").is_ok());
assert!(contains_todo_type("<!-- WARNING: This is a warning -->").is_ok());
assert!(contains_todo_type("<!-- NOTE: This is a note -->").is_ok());
}

#[test]
fn test_todo_types_without_spacing() {
// Handles JS/TS comments
assert!(contains_todo_type("//TODO: This is a todo"));
assert!(contains_todo_type("//FIX: This is a fix"));
assert!(contains_todo_type("//WARNING: This is a warning"));
assert!(contains_todo_type("//NOTE: This is a note"));
assert!(contains_todo_type("//TODO: This is a todo").is_ok());
assert!(contains_todo_type("//FIX: This is a fix").is_ok());
assert!(contains_todo_type("//WARNING: This is a warning").is_ok());
assert!(contains_todo_type("//NOTE: This is a note").is_ok());

// Handles HTML comments
assert!(contains_todo_type("<!--TODO: This is a todo-->"));
assert!(contains_todo_type("<!--FIX: This is a fix-->"));
assert!(contains_todo_type("<!--WARNING: This is a warning-->"));
assert!(contains_todo_type("<!--NOTE: This is a note-->"));
assert!(contains_todo_type("<!--TODO: This is a todo-->").is_ok());
assert!(contains_todo_type("<!--FIX: This is a fix-->").is_ok());
assert!(contains_todo_type("<!--WARNING: This is a warning-->").is_ok());
assert!(contains_todo_type("<!--NOTE: This is a note-->").is_ok());
}
}

0 comments on commit 3335e61

Please sign in to comment.