From 29a31c74b7cad299e0e760bacf3429826ca0ddad Mon Sep 17 00:00:00 2001 From: Payton Swick Date: Thu, 25 Jul 2024 16:23:20 -0400 Subject: [PATCH 1/9] Add serde --- Cargo.lock | 25 +++++++++++++++++++++++++ Cargo.toml | 2 ++ 2 files changed, 27 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index ae7fc40..f30ef9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -300,6 +300,8 @@ dependencies = [ "memchr", "regex", "rstest", + "serde", + "serde_json", "strum", "strum_macros", ] @@ -348,6 +350,12 @@ version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + [[package]] name = "lazy_static" version = "1.5.0" @@ -485,6 +493,12 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "same-file" version = "1.0.6" @@ -520,6 +534,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "slab" version = "0.4.9" diff --git a/Cargo.toml b/Cargo.toml index 685b1bc..ce0cf54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,8 @@ ignore = "0.4.22" memchr = "2.7.4" regex = "1.10.5" rstest = "0.21.0" +serde = { version = "1.0.204", features = ["derive"] } +serde_json = "1.0.120" strum = "0.26.3" strum_macros = "0.26.4" From 9544fdcb3458c317c3762df7046ba75d5495828c Mon Sep 17 00:00:00 2001 From: Payton Swick Date: Thu, 25 Jul 2024 16:23:37 -0400 Subject: [PATCH 2/9] Add tests for search_and_format --- tests/common/mod.rs | 8 +++++++ tests/integration_test.rs | 46 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 51089b1..e7b6be2 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -17,6 +17,7 @@ pub fn make_args( debug: false, no_color: false, threads: None, + format: None, } } @@ -25,6 +26,13 @@ pub fn do_search(args: Args) -> Vec { searcher.search().expect("Search failed for test") } +pub fn do_search_format(args: Args) -> Vec { + let searcher = Searcher::new(args).unwrap(); + searcher + .search_and_format() + .expect("Search failed for test") +} + pub fn get_default_fixture_for_file_type_string(file_type_string: &str) -> Result { match file_type_string { "js" => Ok(String::from("./tests/fixtures/by-language/js-fixture.js")), diff --git a/tests/integration_test.rs b/tests/integration_test.rs index b6c3ee6..57e2e37 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -1,4 +1,4 @@ -use grepdef::{Args, SearchResult}; +use grepdef::{Args, SearchResult, SearchResultFormat}; use rstest::rstest; use std::num::NonZero; @@ -76,6 +76,50 @@ fn to_grep_formats_message_with_number() { } } +#[rstest] +fn search_and_format_returns_formatted_string_for_grep_with_number() { + let file_path = common::get_default_fixture_for_file_type_string("js").unwrap(); + let query = String::from("parseQuery"); + let expected_result = common::get_expected_search_result_for_file_type("js"); + let expected = format!( + "{}:{}:{}", + expected_result.file_path, + expected_result.line_number.unwrap(), + expected_result.text + ); + let file_type_string = String::from("js"); + let mut args = common::make_args(query, Some(file_path), Some(file_type_string)); + args.line_number = true; + args.no_color = true; + args.format = Some(SearchResultFormat::Grep); + let actual = common::do_search_format(args); + for result in actual { + assert_eq!(expected, result); + } +} + +#[rstest] +fn search_and_format_returns_formatted_string_for_json_per_match_with_number() { + let file_path = common::get_default_fixture_for_file_type_string("js").unwrap(); + let query = String::from("parseQuery"); + let expected_result = common::get_expected_search_result_for_file_type("js"); + let expected = format!( + "{{\"file_path\":\"{}\",\"line_number\":{},\"text\":\"{}\"}}", + expected_result.file_path, + expected_result.line_number.unwrap(), + expected_result.text + ); + let file_type_string = String::from("js"); + let mut args = common::make_args(query, Some(file_path), Some(file_type_string)); + args.line_number = true; + args.no_color = true; + args.format = Some(SearchResultFormat::JsonPerMatch); + let actual = common::do_search_format(args); + for result in actual { + assert_eq!(expected, result); + } +} + #[rstest] fn search_returns_matching_js_function_line_with_two_files() { let file_path = format!( From 73cc418b7b079c117f566b5c5b6ae2eccf90ce82 Mon Sep 17 00:00:00 2001 From: Payton Swick Date: Thu, 25 Jul 2024 16:24:59 -0400 Subject: [PATCH 3/9] Add search_and_format and format option --- src/lib.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f72568e..c8154c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,6 +52,7 @@ use clap::Parser; use colored::Colorize; use ignore::Walk; use regex::Regex; +use serde::Serialize; use std::error::Error; use std::fs; use std::io::{self, BufRead, Seek}; @@ -119,6 +120,10 @@ pub struct Args { /// (Advanced) The number of threads to use #[arg(short = 'j', long = "threads")] pub threads: Option>, + + /// The output format; defaults to 'grep' + #[arg(long = "format")] + pub format: Option, } impl Args { @@ -192,6 +197,9 @@ struct Config { /// The number of threads to use for searching files num_threads: NonZero, + + /// The output format + format: SearchResultFormat, } impl Config { @@ -224,6 +232,7 @@ impl Config { no_color: args.no_color, search_method: args.search_method.unwrap_or_default(), num_threads, + format: args.format.unwrap_or_default(), }; debug(&config, format!("Created config {:?}", config).as_str()); Ok(config) @@ -298,12 +307,21 @@ impl FileType { } } +/// The output format of [SearchResult::to_string] +#[derive(clap::ValueEnum, Clone, Default, Debug, EnumString, PartialEq, Display, Copy)] +pub enum SearchResultFormat { + /// grep-like output; colon-separated path, line number, and text + #[default] + Grep, + + /// JSON output; one document per match + JsonPerMatch, +} + /// A result from calling [Searcher::search] /// /// The `line_number` will be set only if [Args::line_number] is true when calling [Searcher::search]. -/// -/// See [SearchResult::to_grep] as the most common formatting output. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Serialize)] pub struct SearchResult { /// The path to the file containing the symbol definition pub file_path: String, @@ -339,6 +357,14 @@ impl SearchResult { None => format!("{}:{}", self.file_path.magenta(), self.text), } } + + /// Return a formatted string for output in the "JSON_PER_MATCH" format + pub fn to_json_per_match(&self) -> String { + match self.line_number { + Some(_) => serde_json::to_string(self).unwrap_or_default(), + None => format!("FIXME JSON {}:{}", self.file_path.magenta(), self.text), + } + } } /// A struct that can perform a search @@ -356,8 +382,8 @@ impl SearchResult { /// true /// )) /// .unwrap(); -/// for result in searcher.search().unwrap() { -/// println!("{}", result.to_grep()); +/// for result in searcher.search_and_format().unwrap() { +/// println!("{}", result); /// } /// ``` pub struct Searcher { @@ -371,7 +397,19 @@ impl Searcher { Ok(Searcher { config }) } - /// Perform the search this struct was built to do + /// Perform the search and return formatted strings + pub fn search_and_format(&self) -> Result, Box> { + let results = self.search()?; + Ok(results + .iter() + .map(|result| match self.config.format { + SearchResultFormat::Grep => result.to_grep(), + SearchResultFormat::JsonPerMatch => result.to_json_per_match(), + }) + .collect()) + } + + /// Perform the search and return [SearchResult] structs pub fn search(&self) -> Result, Box> { // Don't try to even calculate elapsed time if we are not going to print it let start: Option = if self.config.debug { From 70a8b64ab6fb07935477ef6ade79ec7b7a15edaf Mon Sep 17 00:00:00 2001 From: Payton Swick Date: Thu, 25 Jul 2024 16:26:42 -0400 Subject: [PATCH 4/9] Use search_and_format in binary --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 263e7a5..f018e17 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,10 +8,10 @@ fn main() { eprintln!("{err}"); process::exit(exitcode::USAGE); }); - match searcher.search() { + match searcher.search_and_format() { Ok(results) => { for line in results { - println!("{}", line.to_grep()); + println!("{}", line); } } Err(err) => { From 6b4d0801b5bfa660a7e2647e668632b86aa8085a Mon Sep 17 00:00:00 2001 From: Payton Swick Date: Thu, 25 Jul 2024 16:38:21 -0400 Subject: [PATCH 5/9] Add tests for format option with line_number false --- tests/integration_test.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 57e2e37..9fa79ae 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -98,6 +98,23 @@ fn search_and_format_returns_formatted_string_for_grep_with_number() { } } +#[rstest] +fn search_and_format_returns_formatted_string_for_grep_without_number() { + let file_path = common::get_default_fixture_for_file_type_string("js").unwrap(); + let query = String::from("parseQuery"); + let expected_result = common::get_expected_search_result_for_file_type("js"); + let expected = format!("{}:{}", expected_result.file_path, expected_result.text); + let file_type_string = String::from("js"); + let mut args = common::make_args(query, Some(file_path), Some(file_type_string)); + args.line_number = false; + args.no_color = true; + args.format = Some(SearchResultFormat::Grep); + let actual = common::do_search_format(args); + for result in actual { + assert_eq!(expected, result); + } +} + #[rstest] fn search_and_format_returns_formatted_string_for_json_per_match_with_number() { let file_path = common::get_default_fixture_for_file_type_string("js").unwrap(); @@ -120,6 +137,26 @@ fn search_and_format_returns_formatted_string_for_json_per_match_with_number() { } } +#[rstest] +fn search_and_format_returns_formatted_string_for_json_per_match_without_number() { + let file_path = common::get_default_fixture_for_file_type_string("js").unwrap(); + let query = String::from("parseQuery"); + let expected_result = common::get_expected_search_result_for_file_type("js"); + let expected = format!( + "{{\"file_path\":\"{}\",\"line_number\":null,\"text\":\"{}\"}}", + expected_result.file_path, expected_result.text + ); + let file_type_string = String::from("js"); + let mut args = common::make_args(query, Some(file_path), Some(file_type_string)); + args.line_number = false; + args.no_color = true; + args.format = Some(SearchResultFormat::JsonPerMatch); + let actual = common::do_search_format(args); + for result in actual { + assert_eq!(expected, result); + } +} + #[rstest] fn search_returns_matching_js_function_line_with_two_files() { let file_path = format!( From ff8a168cbf8a2949c0f9b32de6f2176e69c7909f Mon Sep 17 00:00:00 2001 From: Payton Swick Date: Thu, 25 Jul 2024 16:38:36 -0400 Subject: [PATCH 6/9] Support to_json_per_match with line_number false --- src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c8154c1..08bff72 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -360,10 +360,7 @@ impl SearchResult { /// Return a formatted string for output in the "JSON_PER_MATCH" format pub fn to_json_per_match(&self) -> String { - match self.line_number { - Some(_) => serde_json::to_string(self).unwrap_or_default(), - None => format!("FIXME JSON {}:{}", self.file_path.magenta(), self.text), - } + serde_json::to_string(self).unwrap_or_default() } } From 7fa41d9c4c507aa2cf0c503982b3d086e813b225 Mon Sep 17 00:00:00 2001 From: Payton Swick Date: Thu, 25 Jul 2024 16:40:33 -0400 Subject: [PATCH 7/9] Adjust docs for SearchResult to mention search_and_format --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 08bff72..0adfb67 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -318,9 +318,9 @@ pub enum SearchResultFormat { JsonPerMatch, } -/// A result from calling [Searcher::search] +/// A result from calling [Searcher::search] or [Searcher::search_and_format] /// -/// The `line_number` will be set only if [Args::line_number] is true when calling [Searcher::search]. +/// Note that `line_number` will be set only if [Args::line_number] is true when searching. #[derive(Debug, PartialEq, Clone, Serialize)] pub struct SearchResult { /// The path to the file containing the symbol definition From 733be0f954654ebfc8359b108c2e531c7087f8cc Mon Sep 17 00:00:00 2001 From: Payton Swick Date: Thu, 25 Jul 2024 16:42:34 -0400 Subject: [PATCH 8/9] Remove weird comment leader in some examples --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0adfb67..ae6edba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,14 +20,14 @@ //! //! ```text //! $ grepdef parseQuery ./src -//! // ./src/queries.js:function parseQuery { +//! ./src/queries.js:function parseQuery { //! ``` //! //! Just like `grep`, you can add the `-n` option to include line numbers. //! //! ```text //! $ grepdef -n parseQuery ./src -//! // ./src/queries.js:17:function parseQuery { +//! ./src/queries.js:17:function parseQuery { //! ``` //! //! The search will be faster if you specify what type of file you are searching for using the @@ -35,7 +35,7 @@ //! //! ```text //! $ grepdef --type js -n parseQuery ./src -//! // ./src/queries.js:17:function parseQuery { +//! ./src/queries.js:17:function parseQuery { //! ``` //! //! To use the crate from other Rust code, use [Searcher]. From bfc326b85e3e40f2cf4f4a80b8b74c94da447e46 Mon Sep 17 00:00:00 2001 From: Payton Swick Date: Thu, 25 Jul 2024 16:44:15 -0400 Subject: [PATCH 9/9] Bump version to 3.1 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f30ef9c..04c4f8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -291,7 +291,7 @@ dependencies = [ [[package]] name = "grepdef" -version = "3.0.0" +version = "3.1.0" dependencies = [ "clap", "colored", diff --git a/Cargo.toml b/Cargo.toml index ce0cf54..40d83e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grepdef" -version = "3.0.0" +version = "3.1.0" edition = "2021" repository = "https://github.com/sirbrillig/grepdef" homepage = "https://github.com/sirbrillig/grepdef"