diff --git a/Cargo.lock b/Cargo.lock index 59a3ea754..91a6f3fbf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -460,6 +460,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "dtoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e" + [[package]] name = "either" version = "1.6.1" @@ -1019,6 +1025,7 @@ dependencies = [ "regex", "serde", "serde_json", + "serde_yaml", "strum", "term_size", "tokei", @@ -1366,6 +1373,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "971be8f6e4d4a47163b405a3df70d14359186f9ab0f3a3ec37df144ca1ce089f" +dependencies = [ + "dtoa", + "linked-hash-map", + "serde", + "yaml-rust", +] + [[package]] name = "sha-1" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index 50f58ebda..9780d9759 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ toml = "0.5.8" yaml-rust = "0.4" serde = "1.0.118" serde_json = "1.0.61" +serde_yaml = "0.8" chrono = "0.4" chrono-humanize = "0.1.1" diff --git a/src/main.rs b/src/main.rs index 0ee440492..d7bbfa9a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,17 +25,11 @@ fn run() -> Result<()> { if !repo::is_valid(&config.repo_path)? { return Err("please run onefetch inside of a non-bare git repository".into()); } - let print_in_json_format = config.print_in_json_format; - let info = info::Info::new(config)?; let mut printer = Printer::new(io::BufWriter::new(io::stdout()), info); - if print_in_json_format { - printer.print_json()? - } else { - printer.print()? - } + printer.print()?; Ok(()) } diff --git a/src/onefetch/cli.rs b/src/onefetch/cli.rs index 5f40ccef9..0d7f68ffe 100644 --- a/src/onefetch/cli.rs +++ b/src/onefetch/cli.rs @@ -5,6 +5,7 @@ use { image_backends, info_field::{InfoField, InfoFieldOff}, language::Language, + printer::SerializationFormat, }, clap::{crate_description, crate_name, crate_version, App, AppSettings, Arg}, image::DynamicImage, @@ -31,10 +32,11 @@ pub struct Cli { pub excluded: Vec, pub print_languages: bool, pub print_package_managers: bool, - pub print_in_json_format: bool, + pub output: Option, pub true_color: bool, pub art_off: bool, pub text_colors: Vec, + pub iso_time: bool, } impl Cli { @@ -60,10 +62,20 @@ impl Cli { .help("Run as if onefetch was started in instead of the current working directory.", )) .arg( - Arg::with_name("json") - .short("j") - .long("json") - .help("Outputs Onefetch in JSON format.") + Arg::with_name("output") + .short("o") + .long("output") + .help("Outputs Onefetch in a specific format (json, yaml).") + .takes_value(true) + .possible_values(&SerializationFormat::iter() + .map(|format| format.into()) + .collect::>()) + ) + .arg( + Arg::with_name("isotime") + .short("z") + .long("isotime") + .help("Outputs Onefetch with ISO 8601 formated timestamps") ) .arg( Arg::with_name("languages") @@ -243,7 +255,9 @@ 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 print_in_json_format = matches.is_present("json"); + let iso_time = matches.is_present("isotime"); + + let output = matches.value_of("output").map(SerializationFormat::from_str).transpose().unwrap(); let fields_to_hide: Vec = if let Some(values) = matches.values_of("disable-fields") { @@ -339,10 +353,11 @@ impl Cli { excluded, print_languages, print_package_managers, - print_in_json_format, + output, true_color, text_colors, art_off, + iso_time, }) } } diff --git a/src/onefetch/info.rs b/src/onefetch/info.rs index 3cc9f6028..d4766c5e1 100644 --- a/src/onefetch/info.rs +++ b/src/onefetch/info.rs @@ -219,10 +219,10 @@ impl Info { let git_username = internal_repo.get_git_username()?; let number_of_tags = internal_repo.get_number_of_tags()?; let number_of_branches = internal_repo.get_number_of_branches()?; - let creation_date = internal_repo.get_creation_date()?; + let creation_date = internal_repo.get_creation_date(config.iso_time)?; let number_of_commits = internal_repo.get_number_of_commits(); let authors = internal_repo.get_authors(config.number_of_authors); - let last_change = internal_repo.get_date_of_last_commit(); + let last_change = internal_repo.get_date_of_last_commit(config.iso_time); let (repo_size, file_count) = internal_repo.get_repo_size(); let workdir = internal_repo.get_work_dir()?; let license = Detector::new()?.get_license(&workdir)?; diff --git a/src/onefetch/printer.rs b/src/onefetch/printer.rs index 54f605e85..382820ad0 100644 --- a/src/onefetch/printer.rs +++ b/src/onefetch/printer.rs @@ -1,9 +1,17 @@ use crate::onefetch::{ascii_art::AsciiArt, error::*, info::Info}; use colored::Color; use std::io::Write; +use strum::{EnumIter, EnumString, IntoStaticStr}; const CENTER_PAD_LENGTH: usize = 3; +#[derive(EnumString, EnumIter, IntoStaticStr)] +#[strum(serialize_all = "lowercase")] +pub enum SerializationFormat { + Json, + Yaml, +} + pub struct Printer { writer: W, info: Info, @@ -15,64 +23,73 @@ impl Printer { } pub fn print(&mut self) -> Result<()> { - let center_pad = " ".repeat(CENTER_PAD_LENGTH); - let info_str = format!("{}", &self.info); - let mut info_lines = info_str.lines(); - let colors: Vec = Vec::new(); - let mut buf = String::new(); + match &self.info.config.output { + Some(format) => match format { + SerializationFormat::Json => { + write!(self.writer, "{}", serde_json::to_string_pretty(&self.info).unwrap())? + } + SerializationFormat::Yaml => { + write!(self.writer, "{}", serde_yaml::to_string(&self.info).unwrap())? + } + }, + None => { + let center_pad = " ".repeat(CENTER_PAD_LENGTH); + let info_str = format!("{}", &self.info); + let mut info_lines = info_str.lines(); + let colors: Vec = Vec::new(); + let mut buf = String::new(); - if self.info.config.art_off { - buf.push_str(&info_str); - } else if let Some(custom_image) = &self.info.config.image { - buf.push_str( - &self - .info - .config - .image_backend - .as_ref() - .unwrap() - .add_image( - info_lines.map(|s| format!("{}{}", center_pad, s)).collect(), - custom_image, - self.info.config.image_color_resolution, - ) - .chain_err(|| "Error while drawing image")?, - ); - } else { - let mut logo_lines = if let Some(custom_ascii) = &self.info.config.ascii_input { - AsciiArt::new(custom_ascii, &colors, !self.info.config.no_bold) - } else { - AsciiArt::new(self.get_ascii(), &self.info.ascii_colors, !self.info.config.no_bold) - }; + if self.info.config.art_off { + buf.push_str(&info_str); + } else if let Some(custom_image) = &self.info.config.image { + buf.push_str( + &self + .info + .config + .image_backend + .as_ref() + .unwrap() + .add_image( + info_lines.map(|s| format!("{}{}", center_pad, s)).collect(), + custom_image, + self.info.config.image_color_resolution, + ) + .chain_err(|| "Error while drawing image")?, + ); + } else { + let mut logo_lines = if let Some(custom_ascii) = &self.info.config.ascii_input { + AsciiArt::new(custom_ascii, &colors, !self.info.config.no_bold) + } else { + AsciiArt::new( + self.get_ascii(), + &self.info.ascii_colors, + !self.info.config.no_bold, + ) + }; - loop { - match (logo_lines.next(), info_lines.next()) { - (Some(logo_line), Some(info_line)) => { - buf.push_str(&format!("{}{}{:^}\n", logo_line, center_pad, info_line)) - } - (Some(logo_line), None) => buf.push_str(&format!("{}\n", logo_line)), - (None, Some(info_line)) => buf.push_str(&format!( - "{: { - buf.push('\n'); - break; + loop { + match (logo_lines.next(), info_lines.next()) { + (Some(logo_line), Some(info_line)) => buf + .push_str(&format!("{}{}{:^}\n", logo_line, center_pad, info_line)), + (Some(logo_line), None) => buf.push_str(&format!("{}\n", logo_line)), + (None, Some(info_line)) => buf.push_str(&format!( + "{: { + buf.push('\n'); + break; + } + } } } + + write!(self.writer, "{}", buf)?; } } - - write!(self.writer, "{}", buf)?; - - Ok(()) - } - - pub fn print_json(&mut self) -> Result<()> { - write!(self.writer, "{}", serde_json::to_string_pretty(&self.info).unwrap())?; Ok(()) } diff --git a/src/onefetch/repo.rs b/src/onefetch/repo.rs index 55b3fcc1e..f1176839c 100644 --- a/src/onefetch/repo.rs +++ b/src/onefetch/repo.rs @@ -32,12 +32,12 @@ impl<'a> Repo<'a> { Ok(logs) } - pub fn get_creation_date(&self) -> Result { + pub fn get_creation_date(&self, iso_time: bool) -> Result { let first_commit = self.logs.last(); let output = match first_commit { Some(commit) => { let time = commit.time(); - utils::git_time_to_human_time(&time) + utils::git_time_to_formatted_time(&time, iso_time) } None => "".into(), }; @@ -84,11 +84,11 @@ impl<'a> Repo<'a> { authors } - pub fn get_date_of_last_commit(&self) -> String { + pub fn get_date_of_last_commit(&self, iso_time: bool) -> String { let last_commit = self.logs.first(); match last_commit { - Some(commit) => utils::git_time_to_human_time(&commit.time()), + Some(commit) => utils::git_time_to_formatted_time(&commit.time(), iso_time), None => "".into(), } } diff --git a/src/onefetch/utils.rs b/src/onefetch/utils.rs index 75e4b4300..2485912ab 100644 --- a/src/onefetch/utils.rs +++ b/src/onefetch/utils.rs @@ -32,7 +32,7 @@ pub fn bytes_to_human_readable(bytes: u128) -> String { byte.get_appropriate_unit(true).to_string() } -pub fn git_time_to_human_time(time: &Time) -> String { +pub fn git_time_to_formatted_time(time: &Time, iso_time: bool) -> String { let (offset, _) = match time.offset_minutes() { n if n < 0 => (-n, '-'), n => (n, '+'), @@ -40,6 +40,10 @@ pub fn git_time_to_human_time(time: &Time) -> String { let offset = FixedOffset::west(offset); let dt_with_tz = offset.timestamp(time.seconds(), 0); - let ht = HumanTime::from(dt_with_tz); - ht.to_text_en(Accuracy::Rough, Tense::Past) + if iso_time { + dt_with_tz.with_timezone(&chrono::Utc).to_rfc3339_opts(chrono::SecondsFormat::Secs, true) + } else { + let ht = HumanTime::from(dt_with_tz); + ht.to_text_en(Accuracy::Rough, Tense::Past) + } }