diff --git a/Cargo.lock b/Cargo.lock index 4cd8165d3..300ebf968 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -953,6 +953,8 @@ dependencies = [ "more-asserts", "paste", "regex", + "serde", + "serde_json", "strum", "term_size", "tokei", @@ -1276,18 +1278,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.117" +version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" +checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.117" +version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" +checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" dependencies = [ "proc-macro2", "quote", @@ -1296,9 +1298,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.58" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a230ea9107ca2220eea9d46de97eddcb04cd00e92d13dda78e478dd33fa82bd4" +checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779" dependencies = [ "itoa", "ryu", diff --git a/Cargo.toml b/Cargo.toml index c898f96d5..b7922bd72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,8 @@ image = "0.23.12" regex = "1" error-chain = "0.12" toml = "0.5.7" +serde = "1.0.118" +serde_json = "1.0.60" [target.'cfg(windows)'.dependencies] ansi_term = "0.12" diff --git a/src/main.rs b/src/main.rs index 7b6d6f268..bbeb84c60 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,11 +30,17 @@ fn run() -> Result<()> { return Err("please run onefetch inside of a non-bare git repository".into()); } + let format = config.format.clone(); + let info = info::Info::new(config)?; let mut printer = Printer::new(io::BufWriter::new(io::stdout()), info); - printer.print()?; + match format.as_str() { + "human" => printer.print()?, + "json" => printer.print_json()?, + _ => printer.print()?, + } Ok(()) } diff --git a/src/onefetch/cli.rs b/src/onefetch/cli.rs index c9b166894..adb0a1637 100644 --- a/src/onefetch/cli.rs +++ b/src/onefetch/cli.rs @@ -31,6 +31,7 @@ pub struct Cli { pub excluded: Vec, pub print_languages: bool, pub print_package_managers: bool, + pub format: String, pub true_color: bool, pub art_off: bool, pub text_colors: Vec, @@ -228,6 +229,15 @@ impl Cli { .takes_value(true) .help("Ignore all files & directories matching EXCLUDE."), ) + .arg( + Arg::with_name("format") + .short("f") + .long("format") + .help("Select a output format.") + .takes_value(true) + .default_value("human") + .possible_values(&["human", "json"]) + ) .get_matches(); let true_color = cli_utils::is_truecolor_terminal(); @@ -236,6 +246,7 @@ impl Cli { let no_color_palette = matches.is_present("no-color-palette"); let print_languages = matches.is_present("languages"); let print_package_managers = matches.is_present("package-managers"); + let format = matches.value_of("format").map(String::from).unwrap(); let fields_to_hide: Vec = if let Some(values) = matches.values_of("disable-fields") { @@ -331,6 +342,7 @@ impl Cli { excluded, print_languages, print_package_managers, + format, true_color, text_colors, art_off, diff --git a/src/onefetch/commit_info.rs b/src/onefetch/commit_info.rs index 84dabcd3e..3233e2f06 100644 --- a/src/onefetch/commit_info.rs +++ b/src/onefetch/commit_info.rs @@ -1,4 +1,6 @@ use git2::Oid; +use serde::ser::SerializeStruct; +use serde::Serialize; pub struct CommitInfo { commit: Oid, @@ -27,3 +29,16 @@ impl std::fmt::Display for CommitInfo { } } } + +impl Serialize for CommitInfo { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("CommitInfo", 2)?; + state.serialize_field("refs", &self.refs)?; + state + .serialize_field("oid", &self.commit.to_string().chars().take(7).collect::())?; + state.end() + } +} diff --git a/src/onefetch/git_utils.rs b/src/onefetch/git_utils.rs index cce0513be..c7c52c3ef 100644 --- a/src/onefetch/git_utils.rs +++ b/src/onefetch/git_utils.rs @@ -74,7 +74,20 @@ pub fn get_number_of_commits(git_history: &[String]) -> String { number_of_commits.to_string() } -pub fn get_packed_size(dir: &str) -> Result { +pub fn get_packed_size(repo_size: String, files_count: Option) -> Result { + match files_count { + Some(files_count) => { + let res = format!("{} ({} files)", repo_size, files_count.to_string()); + Ok(res) + } + None => { + let res = repo_size; + Ok(res.into()) + } + } +} + +pub fn get_repo_size(dir: &str) -> String { let output = Command::new("git") .arg("-C") .arg(dir) @@ -88,10 +101,13 @@ pub fn get_packed_size(dir: &str) -> Result { let size_line = lines.split('\n').find(|line| line.starts_with("size-pack:")); let repo_size = match size_line { - None => "", - Some(size_str) => &(size_str[11..]), + None => String::new(), + Some(size_str) => String::from(&(size_str[11..])), }; + repo_size +} +pub fn get_files_count(dir: &str) -> Option { let output = Command::new("git") .arg("-C") .arg(dir) @@ -106,15 +122,13 @@ pub fn get_packed_size(dir: &str) -> Result { let lines = output.to_string(); let files_list = lines.split('\n'); - let mut files_count: u128 = 0; + let mut files_count: u64 = 0; for _file in files_list { files_count += 1; } files_count -= 1; // As splitting giving one line extra(blank). - let res = repo_size.to_owned() + (" (") + &(files_count.to_string()) + (" files)"); - Ok(res) + Some(files_count) } else { - let res = repo_size; - Ok(res.into()) + None } } diff --git a/src/onefetch/info.rs b/src/onefetch/info.rs index 6bb97ed43..a0291a18f 100644 --- a/src/onefetch/info.rs +++ b/src/onefetch/info.rs @@ -4,6 +4,8 @@ use { language::Language, license::Detector, repo::Repo, text_color::TextColor, }, colored::{Color, ColoredString, Colorize}, + serde::ser::SerializeStruct, + serde::Serialize, }; pub struct Info { @@ -23,6 +25,8 @@ pub struct Info { repo_url: String, number_of_commits: String, lines_of_code: usize, + packed_repo_size: String, + files_count: Option, repo_size: String, license: String, pub dominant_language: Language, @@ -168,12 +172,12 @@ impl std::fmt::Display for Info { )?; } - if !self.config.disabled_fields.size && !self.repo_size.is_empty() { + if !self.config.disabled_fields.size && !self.packed_repo_size.is_empty() { writeln!( f, "{}{}", &self.get_formatted_subtitle_label("Size"), - &self.repo_size.color(self.text_colors.info), + &self.packed_repo_size.color(self.text_colors.info), )?; } @@ -222,7 +226,11 @@ impl Info { let authors = git_utils::get_authors(&git_history, config.number_of_authors); let last_change = git_utils::get_date_of_last_commit(&git_history)?; let git_version = cli_utils::get_git_version()?; - let repo_size = git_utils::get_packed_size(&workdir)?; + + let files_count = git_utils::get_files_count(&workdir); + let repo_size = git_utils::get_repo_size(&workdir); + let packed_repo_size = git_utils::get_packed_size(repo_size.clone(), files_count)?; + let license = Detector::new()?.get_license(&workdir)?; let dependencies = deps::DependencyDetector::new().get_dependencies(&workdir)?; let (languages, lines_of_code) = @@ -253,6 +261,8 @@ impl Info { repo_url, number_of_commits, lines_of_code, + packed_repo_size, + files_count, repo_size, license, dominant_language, @@ -371,3 +381,48 @@ impl Info { } } } + +impl Serialize for Info { + fn serialize(&self, serializer: S) -> serde::export::Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("Info", 21)?; + // Only collect the version number + let git_version_split: Vec = + self.git_version.split(" ").map(|s| s.to_string()).collect(); + + state.serialize_field("gitVersion", &git_version_split[2])?; + state.serialize_field("gitUsername", &self.git_username)?; + state.serialize_field("repoName", &self.repo_name)?; + state.serialize_field("numberOfTags", &self.number_of_tags)?; + state.serialize_field("numberOfBranches", &self.number_of_branches)?; + state.serialize_field("headRefs", &self.head_refs)?; + state.serialize_field("pendingChanges", &self.pending_changes)?; + state.serialize_field("version", &self.version)?; + state.serialize_field("creationDate", &self.creation_date)?; + state.serialize_field("languages", &self.languages)?; + + let dependencies_split: Vec = + self.dependencies.split(" ").map(|s| s.to_string()).collect(); + + state.serialize_field("dependencies", &dependencies_split[0])?; + state.serialize_field("authors", &self.authors)?; + state.serialize_field("lastChange", &self.last_change)?; + state.serialize_field("repoUrl", &self.repo_url)?; + state.serialize_field("numberOfCommits", &self.number_of_commits)?; + state.serialize_field("linesOfCode", &self.lines_of_code)?; + state.serialize_field("packedRepoSize", &self.packed_repo_size)?; + state.serialize_field("repoSize", &self.repo_size)?; + + match &self.files_count { + Some(files_count) => state.serialize_field("filesCount", files_count)?, + None => {} + } + + state.serialize_field("license", &self.license)?; + state.serialize_field("dominantLanguage", &self.dominant_language)?; + state.serialize_field("textColors", &self.text_colors)?; + state.end() + } +} diff --git a/src/onefetch/language.rs b/src/onefetch/language.rs index 1690a2edf..0014a6402 100644 --- a/src/onefetch/language.rs +++ b/src/onefetch/language.rs @@ -2,6 +2,7 @@ use { crate::onefetch::{error::*, utils::num_to_color}, colored::Color, regex::Regex, + serde::Serialize, std::collections::HashMap, std::env, strum::{EnumIter, EnumString, IntoStaticStr}, @@ -22,7 +23,7 @@ macro_rules! define_languages { ($( { $name:ident, $ascii:literal, $display:literal, $colors:expr $(, $serialize:literal )? } ),* ,) => { #[strum(serialize_all = "lowercase")] - #[derive(PartialEq, Eq, Hash, Clone, EnumString, EnumIter, IntoStaticStr)] + #[derive(PartialEq, Eq, Hash, Clone, EnumString, EnumIter, IntoStaticStr, Serialize)] pub enum Language { $( $( #[strum(serialize = $serialize)] )? diff --git a/src/onefetch/mod.rs b/src/onefetch/mod.rs index f7d459699..e80f3c70a 100644 --- a/src/onefetch/mod.rs +++ b/src/onefetch/mod.rs @@ -12,5 +12,6 @@ mod language; mod license; pub mod printer; pub mod repo; +mod serializer; mod text_color; mod utils; diff --git a/src/onefetch/printer.rs b/src/onefetch/printer.rs index c6c2e4efa..54f605e85 100644 --- a/src/onefetch/printer.rs +++ b/src/onefetch/printer.rs @@ -71,6 +71,11 @@ impl Printer { Ok(()) } + pub fn print_json(&mut self) -> Result<()> { + write!(self.writer, "{}", serde_json::to_string_pretty(&self.info).unwrap())?; + Ok(()) + } + fn get_ascii(&self) -> &str { let language = if let Some(ascii_language) = &self.info.config.ascii_language { ascii_language diff --git a/src/onefetch/serializer.rs b/src/onefetch/serializer.rs new file mode 100644 index 000000000..461b0dfbe --- /dev/null +++ b/src/onefetch/serializer.rs @@ -0,0 +1,23 @@ +use {colored::Color, serde::Serialize}; + +#[derive(Serialize)] +#[serde(remote = "Color")] +pub enum ColorDef { + Black, + Red, + Green, + Yellow, + Blue, + Magenta, + Cyan, + White, + BrightBlack, + BrightRed, + BrightGreen, + BrightYellow, + BrightBlue, + BrightMagenta, + BrightCyan, + BrightWhite, + TrueColor { r: u8, g: u8, b: u8 }, +} diff --git a/src/onefetch/text_color.rs b/src/onefetch/text_color.rs index 2226a7017..46eb1a0a1 100644 --- a/src/onefetch/text_color.rs +++ b/src/onefetch/text_color.rs @@ -1,11 +1,21 @@ -use {crate::onefetch::utils::num_to_color, colored::Color}; +use { + crate::onefetch::serializer::ColorDef, crate::onefetch::utils::num_to_color, colored::Color, + serde::Serialize, +}; +#[derive(Serialize)] pub struct TextColor { + #[serde(with = "ColorDef")] pub title: Color, + #[serde(with = "ColorDef")] pub tilde: Color, + #[serde(with = "ColorDef")] pub underline: Color, + #[serde(with = "ColorDef")] pub subtitle: Color, + #[serde(with = "ColorDef")] pub colon: Color, + #[serde(with = "ColorDef")] pub info: Color, }