Skip to content

Commit

Permalink
Merge pull request #34 from sirbrillig/add-json-output
Browse files Browse the repository at this point in the history
Add json output
  • Loading branch information
sirbrillig authored Jul 26, 2024
2 parents bc4fdb9 + bfc326b commit 07227f7
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 16 deletions.
27 changes: 26 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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"

Expand Down
57 changes: 46 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,22 @@
//!
//! ```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
//! `--type` option.
//!
//! ```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].
Expand All @@ -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};
Expand Down Expand Up @@ -119,6 +120,10 @@ pub struct Args {
/// (Advanced) The number of threads to use
#[arg(short = 'j', long = "threads")]
pub threads: Option<NonZero<usize>>,

/// The output format; defaults to 'grep'
#[arg(long = "format")]
pub format: Option<SearchResultFormat>,
}

impl Args {
Expand Down Expand Up @@ -192,6 +197,9 @@ struct Config {

/// The number of threads to use for searching files
num_threads: NonZero<usize>,

/// The output format
format: SearchResultFormat,
}

impl Config {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -298,12 +307,21 @@ impl FileType {
}
}

/// A result from calling [Searcher::search]
///
/// The `line_number` will be set only if [Args::line_number] is true when calling [Searcher::search].
/// 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] or [Searcher::search_and_format]
///
/// See [SearchResult::to_grep] as the most common formatting output.
#[derive(Debug, PartialEq, Clone)]
/// 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
pub file_path: String,
Expand Down Expand Up @@ -339,6 +357,11 @@ 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 {
serde_json::to_string(self).unwrap_or_default()
}
}

/// A struct that can perform a search
Expand All @@ -356,8 +379,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 {
Expand All @@ -371,7 +394,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<Vec<String>, Box<dyn Error>> {
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<Vec<SearchResult>, Box<dyn Error>> {
// Don't try to even calculate elapsed time if we are not going to print it
let start: Option<time::Instant> = if self.config.debug {
Expand Down
4 changes: 2 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
8 changes: 8 additions & 0 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub fn make_args(
debug: false,
no_color: false,
threads: None,
format: None,
}
}

Expand All @@ -25,6 +26,13 @@ pub fn do_search(args: Args) -> Vec<SearchResult> {
searcher.search().expect("Search failed for test")
}

pub fn do_search_format(args: Args) -> Vec<String> {
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<String, String> {
match file_type_string {
"js" => Ok(String::from("./tests/fixtures/by-language/js-fixture.js")),
Expand Down
83 changes: 82 additions & 1 deletion tests/integration_test.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use grepdef::{Args, SearchResult};
use grepdef::{Args, SearchResult, SearchResultFormat};
use rstest::rstest;
use std::num::NonZero;

Expand Down Expand Up @@ -76,6 +76,87 @@ 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_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();
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_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!(
Expand Down

0 comments on commit 07227f7

Please sign in to comment.