Skip to content

Commit

Permalink
Add JSON output option to CLI (#294)
Browse files Browse the repository at this point in the history
  • Loading branch information
spenserblack committed Jan 26, 2024
2 parents 76444ff + 827dc7a commit 0d21cd1
Show file tree
Hide file tree
Showing 10 changed files with 157 additions and 18 deletions.
9 changes: 6 additions & 3 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ resolver = "2"

[workspace.package]
description = "Get the language distribution stats of your repository"
version = "0.10.0"
version = "0.10.1"
edition = "2021"
repository = "https://github.com/spenserblack/gengo"
readme = "README.md"
Expand Down
4 changes: 3 additions & 1 deletion gengo-bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ clap = { version = "4", features = ["derive", "wrap_help"] }
gengo = { path = "../gengo", version = "0.10", default-features = false }
indexmap = "2"
owo-colors = { version = ">=3, <=4", optional = true }
serde_json = "1"

[dev-dependencies]
insta = "1"
insta = { version = "1", features = ["json"] }
serde_json = "1"
40 changes: 39 additions & 1 deletion gengo-bin/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use clap::Error as ClapError;
use clap::{Parser, Subcommand};
use clap::{Parser, Subcommand, ValueEnum};
use gengo::{analysis::SummaryOpts, Analysis, Builder, Directory, Git};
use indexmap::IndexMap;
use std::error::Error as BaseError;
Expand Down Expand Up @@ -28,15 +28,24 @@ pub struct CLI {
#[arg(short = 'l', long, default_value = "1048576")]
read_limit: usize,
/// Report on all files, even if they are not detectable.
///
/// This only applies to the pretty format, as machine-readable
/// formats always include all files.
#[arg(short = 'a', long)]
all: bool,
/// Include detailed statistics for each language.
///
/// This only applies to the pretty format, as machine-readable
/// formats always include detailed statistics.
#[arg(short = 'b', long)]
breakdown: bool,
/// Force the output to not have colors.
#[cfg(feature = "color")]
#[arg(long)]
no_color: bool,
/// The format to use for output.
#[arg(short = 'F', long, default_value = "pretty")]
format: Format,
}

#[derive(Subcommand)]
Expand All @@ -58,6 +67,14 @@ enum Commands {
},
}

#[derive(ValueEnum, Debug, Clone)]
enum Format {
/// Output for humans.
Pretty,
/// JSON output.
Json,
}

impl CLI {
pub fn run(&self, mut out: impl Write, mut err: impl Write) -> Result<(), io::Error> {
let results = self.command.analyze(self.read_limit);
Expand All @@ -69,6 +86,11 @@ impl CLI {
}
};

match self.format {
Format::Pretty => (),
Format::Json => return self.run_json(results, out, err),
}

let mut summary_opts: SummaryOpts = Default::default();
summary_opts.all = self.all;
let summary = results.summary_with(summary_opts);
Expand Down Expand Up @@ -102,6 +124,22 @@ impl CLI {
Ok(())
}

fn run_json(
&self,
analysis: Analysis,
mut out: impl Write,
mut _err: impl Write,
) -> Result<(), io::Error> {
match serde_json::to_string(&analysis) {
Ok(s) => writeln!(out, "{s}")?,
Err(e) => {
writeln!(out, "failed to serialize to JSON: {e}")?;
return Ok(());
}
};
Ok(())
}

fn run_breakdown(
&self,
mut out: impl Write,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
source: gengo-bin/tests/test_cli.rs
expression: json
---
{
"bin.js": {
"detectable": true,
"documentation": false,
"generated": false,
"language": {
"category": "programming",
"color": "#F0DC4E",
"name": "JavaScript"
},
"size": 28,
"vendored": false
},
"dist/bin.js": {
"detectable": true,
"documentation": false,
"generated": true,
"language": {
"category": "prose",
"color": "#000000",
"name": "Plain Text"
},
"size": 62,
"vendored": false
},
"docs/index.html": {
"detectable": false,
"documentation": true,
"generated": false,
"language": {
"category": "markup",
"color": "#E96228",
"name": "HTML"
},
"size": 26,
"vendored": false
},
"node_modules/my-dependency/index.js": {
"detectable": false,
"documentation": false,
"generated": false,
"language": {
"category": "programming",
"color": "#F0DC4E",
"name": "JavaScript"
},
"size": 29,
"vendored": true
},
"src/bin.ts": {
"detectable": true,
"documentation": false,
"generated": false,
"language": {
"category": "programming",
"color": "#2F74C0",
"name": "TypeScript"
},
"size": 62,
"vendored": false
}
}
31 changes: 29 additions & 2 deletions gengo-bin/tests/test_cli.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use insta::assert_snapshot;
use insta::{assert_json_snapshot, assert_snapshot};
use std::io::{self, Write};

const ROOT: &str = env!("CARGO_MANIFEST_DIR");
Expand All @@ -25,6 +25,19 @@ macro_rules! assert_stdout_snapshot {
}};
}

// TODO This feels like a bit of a hack.
macro_rules! assert_stdout_json_snapshot {
($cli_args:expr $(,)?) => {{
let cli = gengo_bin::cli::try_new_from($cli_args).unwrap();
let mut stdout = Vec::new();
let mut stderr = NullWriter;
cli.run(&mut stdout, &mut stderr).unwrap();
let stdout = String::from_utf8(stdout).unwrap();
let json: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert_json_snapshot!(json);
}};
}

#[test]
#[cfg(not(feature = "color"))]
fn test_javascript_repo() {
Expand Down Expand Up @@ -67,5 +80,19 @@ fn test_color_breakdown_javascript_repo() {
]);
}

//
// TODO Add test_javascript_repo_windows

#[test]
#[cfg_attr(windows, ignore)]
fn test_json_output_on_javascript_repo() {
assert_stdout_json_snapshot!(&[
"gengo",
"--format",
"json",
"git",
"-r",
"test/javascript",
"-R",
ROOT,
]);
}
2 changes: 1 addition & 1 deletion gengo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ gix = { version = ">= 0.56, <= 0.57", default-features = false, features = [
] }
glob = "0.3"
ignore = "0.4"
indexmap = { version = "2", features = ["serde"] }
indexmap = { version = "2", features = ["rayon", "serde"] }
once_cell = "1"
owo-colors = { version = ">=3, <=4", optional = true }
rayon = "1"
Expand Down
7 changes: 4 additions & 3 deletions gengo/src/analysis/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::Entry;
use indexmap::IndexMap;
use serde::Serialize;

use std::path::PathBuf;

Expand All @@ -10,11 +11,11 @@ pub use summary::Summary;
mod summary;

/// The result of analyzing a repository along with all of its submodules.
#[derive(Debug)]
pub struct Analysis(pub(super) Vec<(PathBuf, Entry)>);
#[derive(Debug, Serialize)]
pub struct Analysis(pub(super) IndexMap<PathBuf, Entry>);

impl Analysis {
pub fn iter(&self) -> impl Iterator<Item = &(PathBuf, Entry)> {
pub fn iter(&self) -> impl Iterator<Item = (&PathBuf, &Entry)> {
let results = &self.0;
results.iter()
}
Expand Down
12 changes: 7 additions & 5 deletions gengo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use generated::Generated;

pub use file_source::{Directory, FileSource, Git};
use glob::MatchOptions;
use indexmap::IndexMap;
pub use languages::analyzer::Analyzers;
use languages::Category;
pub use languages::Language;
Expand All @@ -35,7 +36,8 @@ use std::path::Path;

use vendored::Vendored;

use rayon::prelude::{ParallelBridge, ParallelIterator};
use rayon::prelude::{FromParallelIterator, ParallelBridge, ParallelIterator};
use serde::Serialize;

pub mod analysis;
mod builder;
Expand Down Expand Up @@ -70,7 +72,7 @@ impl<FS: for<'fs> FileSource<'fs>> Gengo<FS> {
/// Analyzes each file in the repository at the given revision.
pub fn analyze(&self) -> Result<Analysis> {
let state = self.file_source.state()?;
let entries: Vec<(_, _)> = self
let entries = self
.file_source
.entries()?
.par_bridge()
Expand All @@ -80,8 +82,8 @@ impl<FS: for<'fs> FileSource<'fs>> Gengo<FS> {
let entry = self.analyze_blob(&filepath, contents, state)?;
Some((filepath.as_ref().to_owned(), entry))
})
.filter_map(|entry| entry)
.collect();
.filter_map(|entry| entry);
let entries = IndexMap::from_par_iter(entries);

Ok(Analysis(entries))
}
Expand Down Expand Up @@ -148,7 +150,7 @@ impl<FS: for<'fs> FileSource<'fs>> Gengo<FS> {
}

/// A single entry in the language statistics.
#[derive(Debug)]
#[derive(Debug, Serialize)]
pub struct Entry {
/// The detected language.
language: Language,
Expand Down
2 changes: 1 addition & 1 deletion gengo/tests/gengo_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ fn test_git_javascript() {
let gengo = Builder::new(git).analyzers(analyzers).build().unwrap();
let analysis = gengo.analyze().unwrap();
let mut results: Vec<_> = analysis.iter().collect();
results.sort_by_key(|(path, _)| path);
results.sort_by_key(|(path, _)| path.to_owned());
insta::assert_debug_snapshot!(results);
}

Expand Down

0 comments on commit 0d21cd1

Please sign in to comment.