Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nit: Strict clippy #28

Merged
merged 3 commits into from
Jan 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
name = "tag"
version = "0.1.0"
edition = "2021"
description = "Search for local text files with a simple tagging system."
license = "MIT"
repository = "https://github.com/miampf/tag"
keywords = ["organization", "tagging", "plaintext", "searching"]
categories = ["command-line-utilities", "filesystem"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Expand All @@ -13,3 +18,11 @@ pest = "2.7.6"
pest_derive = "2.7.6"
textwrap = "0.16.0"
walkdir = "2.4.0"

[lints.clippy]
pedantic = "warn"
nursery = "warn"
suspicious = "warn"
complexity = "deny"
perf = "deny"
style = "deny"
30 changes: 14 additions & 16 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::{path::PathBuf, process::Command};
use std::{path::Path, process::Command};

use colored::Colorize;
use pest::Parser;
use tag::{
parsers::query::{construct_query_ast, evaluate_ast, QueryParser, Rule},
parsers::searchquery::{construct_query_ast, evaluate_ast, QueryParser, Rule},
search::get_tags_from_files,
};

Expand Down Expand Up @@ -39,13 +39,13 @@ mod cli {
}

impl Cli {
pub fn new_and_parse() -> Cli {
Cli::parse()
pub fn new_and_parse() -> Self {
Self::parse()
}
}
}

fn execute_command_on_file(path: PathBuf, command: String) -> String {
fn execute_command_on_file(path: &Path, command: &str) -> String {
let command = command.replace("#FILE#", path.to_str().unwrap());

let output = if cfg!(target_os = "windows") {
Expand Down Expand Up @@ -78,7 +78,7 @@ fn execute_command_on_file(path: PathBuf, command: String) -> String {
output_string.unwrap().to_string()
}

fn execute_filter_command_on_file(path: PathBuf, command: String) -> bool {
fn execute_filter_command_on_file(path: &Path, command: &str) -> bool {
let command = command.replace("#FILE#", path.to_str().unwrap());

let output = if cfg!(target_os = "windows") {
Expand Down Expand Up @@ -121,10 +121,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {

let query = query.unwrap();

for file in file_index.iter() {
for file in file_index {
let ast = construct_query_ast(
query.clone().next().unwrap().into_inner(),
file.tags.iter().map(|tag| tag.as_str()).collect(),
&file.tags.iter().map(std::string::String::as_str).collect(),
);

// skip the file if tags don't match query
Expand All @@ -134,20 +134,18 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {

// skip the file if filter command is unsuccessful
if args.filter_command.is_some()
&& !execute_filter_command_on_file(
file.path.clone(),
args.filter_command.clone().unwrap(),
)
&& !execute_filter_command_on_file(&file.path, &args.filter_command.clone().unwrap())
{
continue;
}

println!("{}", file.path.display().to_string().green());

let mut output = String::new();
if args.command.is_some() {
output = execute_command_on_file(file.path.clone(), args.command.clone().unwrap());
}
let output = if args.command.is_some() {
execute_command_on_file(&file.path, &args.command.clone().unwrap())
} else {
String::new()
};

if !args.silent {
println!("\t{}", format!("tags: {:?}", file.tags).blue());
Expand Down
73 changes: 38 additions & 35 deletions src/parsers.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
pub mod tagline {
pub mod onfile {
use pest_derive::Parser;

#[derive(Parser)]
#[grammar = "tagline.pest"]
/// TaglineParser is responsible for parsing the taglines at the start of each searched file.
/// `TaglineParser` is responsible for parsing the taglines at the start of each searched file.
/// The relevant rule is `tagline`.
pub struct TaglineParser;
}

pub mod query {
pub mod searchquery {
use pest::{iterators::Pairs, pratt_parser::PrattParser};
use pest_derive::Parser;

Expand All @@ -25,16 +25,16 @@ pub mod query {
}

/// Op is an Operation that can be used in a query.
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Op {
And,
Or,
}

lazy_static::lazy_static! {
static ref PRATT_PARSER: PrattParser<Rule> = {
use pest::pratt_parser::{Assoc::*, Op};
use Rule::*;
use pest::pratt_parser::{Assoc::Left, Op};
use Rule::{and, or, unary_not};

PrattParser::new()
// & and | are evaluated with the same precedence
Expand All @@ -45,17 +45,18 @@ pub mod query {

#[derive(Parser)]
#[grammar = "query.pest"]
/// QueryParser is responsible for parsing the search query.
/// `QueryParser` is responsible for parsing the search query.
/// The relevant rule is `tagsearch`.
pub struct QueryParser;

/// construct_query_ast() creates an AST from a string of symbols
/// lexed by the QueryParser and a list of tags.
pub fn construct_query_ast(pairs: Pairs<Rule>, tags: Vec<&str>) -> Expr {
/// `construct_query_ast()` creates an AST from a string of symbols
/// lexed by the `QueryParser` and a list of tags.
#[must_use]
pub fn construct_query_ast(pairs: Pairs<Rule>, tags: &Vec<&str>) -> Expr {
PRATT_PARSER
.map_primary(|primary| match primary.as_rule() {
Rule::tag => Expr::Bool(tags.contains(&primary.as_str().trim())),
Rule::expr => construct_query_ast(primary.into_inner(), tags.clone()),
Rule::expr => construct_query_ast(primary.into_inner(), tags),
rule => unreachable!("Expected tag, found {:?}", rule),
})
.map_infix(|lhs, op, rhs| {
Expand All @@ -78,8 +79,9 @@ pub mod query {
.parse(pairs)
}

/// evaluate_ast() evaluates an AST created by construct_query_ast()
/// `evaluate_ast()` evaluates an AST created by `construct_query_ast()`
/// and returns the result.
#[must_use]
pub fn evaluate_ast(ast: Expr) -> bool {
match ast {
Expr::Bool(value) => value,
Expand All @@ -98,14 +100,14 @@ pub mod query {

#[cfg(test)]
mod tests {
use crate::parsers::query::construct_query_ast;
use crate::parsers::query::evaluate_ast;
use crate::parsers::query::Expr;
use crate::parsers::query::Op;
use crate::parsers::query::QueryParser;
use crate::parsers::searchquery::construct_query_ast;
use crate::parsers::searchquery::evaluate_ast;
use crate::parsers::searchquery::Expr;
use crate::parsers::searchquery::Op;
use crate::parsers::searchquery::QueryParser;

use super::query;
use super::tagline;
use super::onfile;
use super::searchquery;

use pest::Parser;

Expand Down Expand Up @@ -151,10 +153,10 @@ mod tests {
},
];

test_cases.iter().for_each(|test_case| {
for test_case in test_cases {
println!("test_tagline_parser: \n\t{}", test_case.name);

let res = tagline::TaglineParser::parse(tagline::Rule::tagline, test_case.input);
let res = onfile::TaglineParser::parse(onfile::Rule::tagline, test_case.input);
if res.is_err() {
assert!(test_case.expected_error);
return;
Expand All @@ -163,11 +165,11 @@ mod tests {
assert!(!test_case.expected_error);

for (i, tag) in res.unwrap().enumerate() {
if tag.as_rule() == tagline::Rule::tag {
if tag.as_rule() == onfile::Rule::tag {
assert_eq!(tag.as_str().trim(), test_case.expected_tags[i]);
}
}
})
}
}

#[test]
Expand Down Expand Up @@ -221,19 +223,20 @@ mod tests {
},
];

test_cases.iter().for_each(|test_case| {
for test_case in test_cases {
println!("test_query_parser: \n\t{}", test_case.name);

let res = query::QueryParser::parse(query::Rule::tagsearch, test_case.input);
let res =
searchquery::QueryParser::parse(searchquery::Rule::tagsearch, test_case.input);
if res.is_err() {
assert!(test_case.expected_error);
return;
}

assert!(!test_case.expected_error);

assert_eq!(test_case.input, res.unwrap().as_str())
})
assert_eq!(test_case.input, res.unwrap().as_str());
}
}

#[test]
Expand Down Expand Up @@ -276,24 +279,24 @@ mod tests {
},
];

test_cases.iter().for_each(|test_case| {
for test_case in test_cases {
println!("test_construct_query_ast: \n\t{}", test_case.name);

let ast = construct_query_ast(
QueryParser::parse(query::Rule::tagsearch, test_case.input_query)
QueryParser::parse(searchquery::Rule::tagsearch, test_case.input_query)
.unwrap()
.next()
.unwrap()
.into_inner(),
test_case
&test_case
.input_tags
.iter()
.map(|tag| tag.as_str())
.map(std::string::String::as_str)
.collect(),
);

assert_eq!(test_case.expected_ast, ast);
})
}
}

#[test]
Expand Down Expand Up @@ -333,13 +336,13 @@ mod tests {
},
];

test_cases.iter().for_each(|test_case| {
for test_case in test_cases {
println!("test_evaluate_ast: \n\t{}", test_case.name);

assert_eq!(
test_case.expected_result,
evaluate_ast(test_case.input_ast.clone())
)
})
);
}
}
}
20 changes: 12 additions & 8 deletions src/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,42 @@ use std::{
use pest::Parser;
use walkdir::WalkDir;

use crate::parsers::tagline::{self, TaglineParser};
use crate::parsers::onfile::{self, TaglineParser};

/// TaggedFile is a file that contains tags.
/// `TaggedFile` is a file that contains tags.
#[derive(Clone, Debug)]
pub struct TaggedFile {
pub path: PathBuf,
pub tags: Vec<String>,
}

/// get_tags_from_file() returns a list of tags found in a file.
/// `get_tags_from_file()` returns a list of tags found in a file.
/// It will return an error if a file has no parsable tags.
fn get_tags_from_file(file: &Path) -> Result<Vec<String>, Box<dyn std::error::Error>> {
let file = fs::File::open(file)?;
let mut buffer = BufReader::new(file);
let mut tagline = String::new();
let _ = buffer.read_line(&mut tagline)?;

let parsed = TaglineParser::parse(tagline::Rule::tagline, tagline.trim())?;
let parsed = TaglineParser::parse(onfile::Rule::tagline, tagline.trim())?;

let mut tags = Vec::new();

for tag in parsed {
if tag.as_rule() == tagline::Rule::tag {
tags.push(tag.as_str().to_string())
if tag.as_rule() == onfile::Rule::tag {
tags.push(tag.as_str().to_string());
}
}

Ok(tags)
}

/// get_tags_from_files() recursively retrieves the tags of all files
/// `get_tags_from_files()` recursively retrieves the tags of all files
/// in a given directory.
///
/// # Errors
///
/// This function errors if it fails to walk the given directory.
pub fn get_tags_from_files(directory: &str) -> Result<Vec<TaggedFile>, Box<dyn std::error::Error>> {
let mut tagged_files = Vec::new();

Expand All @@ -55,7 +59,7 @@ pub fn get_tags_from_files(directory: &str) -> Result<Vec<TaggedFile>, Box<dyn s
tagged_files.push(TaggedFile {
path: entry.path().to_owned(),
tags,
})
});
}
}

Expand Down