From 62697c41aeba26bbcfa60eb593164c52b0403aa5 Mon Sep 17 00:00:00 2001 From: Ossama Hjaji Date: Sat, 10 Oct 2020 00:18:26 +0200 Subject: [PATCH 01/14] split/refacto main module --- src/app.rs | 108 +++++++++++++++++++ src/exit_codes.rs | 14 +++ src/info.rs | 131 ++++++++++++---------- src/language.rs | 9 +- src/main.rs | 268 +++++++++++----------------------------------- src/options.rs | 30 ++++++ 6 files changed, 291 insertions(+), 269 deletions(-) create mode 100644 src/app.rs create mode 100644 src/exit_codes.rs create mode 100644 src/options.rs diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 000000000..93bd629b8 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,108 @@ +use { + crate::info::InfoFields, + clap::{App, AppSettings, Arg}, + strum::{EnumCount, IntoEnumIterator}, +}; + +pub fn build_app() -> App<'static, 'static> { + #[cfg(target_os = "linux")] + let possible_backends = ["kitty", "sixel"]; + #[cfg(not(target_os = "linux"))] + let possible_backends = []; + + App::new(crate_name!()) + .version(crate_version!()) + .about(crate_description!()) + .setting(AppSettings::ColoredHelp) + .setting(AppSettings::DeriveDisplayOrder) + .arg(Arg::with_name("input").default_value(".").help( + "Run as if onefetch was started in instead of the current working directory.", + )) + .arg( + Arg::with_name("ascii-language") + .short("a") + .long("ascii-language") + .takes_value(true) + .case_insensitive(true) + .help("Which language's ascii art to print."), + ) + .arg( + Arg::with_name("disable-fields") + .long("disable-fields") + .short("d") + .multiple(true) + .takes_value(true) + .case_insensitive(true) + .help("Allows you to disable an info line from appearing in the output.") + .possible_values( + &InfoFields::iter() + .take(InfoFields::COUNT - 1) + .map(|field| field.into()) + .collect::>() + .as_slice(), + ), + ) + .arg( + Arg::with_name("ascii-colors") + .short("c") + .long("ascii-colors") + .multiple(true) + .takes_value(true) + .possible_values(&[ + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", + "15", + ]) + .hide_possible_values(true), + ) + .arg( + Arg::with_name("no-bold") + .long("no-bold") + .help("Turns off bold formatting."), + ) + .arg( + Arg::with_name("languages") + .short("l") + .long("languages") + .help("Prints out supported languages"), + ) + .arg( + Arg::with_name("image") + .short("i") + .long("image") + .takes_value(true) + .help("Which image to use. Possible values: [/path/to/img]"), + ) + .arg( + Arg::with_name("image-backend") + .long("image-backend") + .takes_value(true) + .possible_values(&possible_backends) + .help("Which image backend to use."), + ) + .arg( + Arg::with_name("no-merge-commits") + .long("no-merge-commits") + .help("Ignores merge commits"), + ) + .arg( + Arg::with_name("no-color-blocks") + .long("no-color-blocks") + .help("Hides the color blocks"), + ) + .arg( + Arg::with_name("authors-number") + .short("A") + .long("authors-number") + .takes_value(true) + .default_value("3") + .help("Number of authors to be shown."), + ) + .arg( + Arg::with_name("exclude") + .short("e") + .long("exclude") + .multiple(true) + .takes_value(true) + .help("Ignore all files & directories matching the pattern."), + ) +} diff --git a/src/exit_codes.rs b/src/exit_codes.rs new file mode 100644 index 000000000..9c85d9ce8 --- /dev/null +++ b/src/exit_codes.rs @@ -0,0 +1,14 @@ +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum ExitCode { + Success, + GeneralError, +} + +impl Into for ExitCode { + fn into(self) -> i32 { + match self { + ExitCode::Success => 0, + ExitCode::GeneralError => 1, + } + } +} diff --git a/src/info.rs b/src/info.rs index 9558fd10d..f45e7931c 100644 --- a/src/info.rs +++ b/src/info.rs @@ -1,15 +1,15 @@ use { crate::{ - image_backends::ImageBackend, language::Language, license::Detector, - {AsciiArt, CommitInfo, Error, InfoFieldOn}, + options::Options, + {AsciiArt, CommitInfo, Error}, }, colored::{Color, ColoredString, Colorize}, git2::Repository, - image::DynamicImage, regex::Regex, std::{ffi::OsStr, fmt::Write, fs}, + strum::{EnumCount, EnumIter, EnumString, IntoStaticStr}, tokio::process::Command, }; @@ -17,6 +17,44 @@ type Result = std::result::Result; const LICENSE_FILES: [&str; 3] = ["LICENSE", "LICENCE", "COPYING"]; +#[derive(Default)] +pub struct InfoFieldOn { + pub git_info: bool, + pub project: bool, + pub head: bool, + pub version: bool, + pub created: bool, + pub languages: bool, + pub authors: bool, + pub last_change: bool, + pub repo: bool, + pub commits: bool, + pub pending: bool, + pub lines_of_code: bool, + pub size: bool, + pub license: bool, +} + +#[derive(PartialEq, Eq, EnumString, EnumCount, EnumIter, IntoStaticStr)] +#[strum(serialize_all = "snake_case")] +pub enum InfoFields { + GitInfo, + Project, + HEAD, + Version, + Created, + Languages, + Authors, + LastChange, + Repo, + Commits, + Pending, + LinesOfCode, + Size, + License, + UnrecognizedField, +} + pub struct Info { git_version: String, git_username: String, @@ -36,13 +74,7 @@ pub struct Info { number_of_tags: usize, number_of_branches: usize, license: String, - custom_logo: Language, - custom_colors: Vec, - disable_fields: InfoFieldOn, - bold_enabled: bool, - no_color_blocks: bool, - custom_image: Option, - image_backend: Option>, + config: Options, } impl std::fmt::Display for Info { @@ -52,7 +84,7 @@ impl std::fmt::Display for Info { Some(&c) => c, None => Color::White, }; - if !self.disable_fields.git_info { + if !self.config.disabled_fields.git_info { let git_info_length; if self.git_username != "" { git_info_length = self.git_username.len() + self.git_version.len() + 3; @@ -76,7 +108,7 @@ impl std::fmt::Display for Info { &separator, )?; } - if !self.disable_fields.project { + if !self.config.disabled_fields.project { let branches_str = match self.number_of_branches { 0 => String::new(), 1 => String::from("1 branch"), @@ -106,7 +138,7 @@ impl std::fmt::Display for Info { )?; } - if !self.disable_fields.head { + if !self.config.disabled_fields.head { write_buf( &mut buf, &self.get_formatted_info_label("HEAD: ", color), @@ -114,7 +146,7 @@ impl std::fmt::Display for Info { )?; } - if !self.disable_fields.pending && self.pending != "" { + if !self.config.disabled_fields.pending && self.pending != "" { write_buf( &mut buf, &self.get_formatted_info_label("Pending: ", color), @@ -122,7 +154,7 @@ impl std::fmt::Display for Info { )?; } - if !self.disable_fields.version { + if !self.config.disabled_fields.version { write_buf( &mut buf, &self.get_formatted_info_label("Version: ", color), @@ -130,7 +162,7 @@ impl std::fmt::Display for Info { )?; } - if !self.disable_fields.created { + if !self.config.disabled_fields.created { write_buf( &mut buf, &self.get_formatted_info_label("Created: ", color), @@ -138,7 +170,7 @@ impl std::fmt::Display for Info { )?; } - if !self.disable_fields.languages && !self.languages.is_empty() { + if !self.config.disabled_fields.languages && !self.languages.is_empty() { if self.languages.len() > 1 { let title = "Languages: "; let pad = " ".repeat(title.len()); @@ -173,7 +205,7 @@ impl std::fmt::Display for Info { }; } - if !self.disable_fields.authors && !self.authors.is_empty() { + if !self.config.disabled_fields.authors && !self.authors.is_empty() { let title = if self.authors.len() > 1 { "Authors: " } else { @@ -203,7 +235,7 @@ impl std::fmt::Display for Info { } } - if !self.disable_fields.last_change { + if !self.config.disabled_fields.last_change { write_buf( &mut buf, &self.get_formatted_info_label("Last change: ", color), @@ -211,7 +243,7 @@ impl std::fmt::Display for Info { )?; } - if !self.disable_fields.repo { + if !self.config.disabled_fields.repo { write_buf( &mut buf, &self.get_formatted_info_label("Repo: ", color), @@ -219,7 +251,7 @@ impl std::fmt::Display for Info { )?; } - if !self.disable_fields.commits { + if !self.config.disabled_fields.commits { write_buf( &mut buf, &self.get_formatted_info_label("Commits: ", color), @@ -227,7 +259,7 @@ impl std::fmt::Display for Info { )?; } - if !self.disable_fields.lines_of_code { + if !self.config.disabled_fields.lines_of_code { write_buf( &mut buf, &self.get_formatted_info_label("Lines of code: ", color), @@ -235,7 +267,7 @@ impl std::fmt::Display for Info { )?; } - if !self.disable_fields.size { + if !self.config.disabled_fields.size { write_buf( &mut buf, &self.get_formatted_info_label("Size: ", color), @@ -243,7 +275,7 @@ impl std::fmt::Display for Info { )?; } - if !self.disable_fields.license { + if !self.config.disabled_fields.license { write_buf( &mut buf, &self.get_formatted_info_label("License: ", color), @@ -251,7 +283,7 @@ impl std::fmt::Display for Info { )?; } - if !self.no_color_blocks { + if !self.config.no_color_blocks { writeln!( buf, "\n{0}{1}{2}{3}{4}{5}{6}{7}", @@ -269,8 +301,8 @@ impl std::fmt::Display for Info { let center_pad = " "; let mut info_lines = buf.lines(); - if let Some(custom_image) = &self.custom_image { - if let Some(image_backend) = &self.image_backend { + if let Some(custom_image) = &self.config.image { + if let Some(image_backend) = &self.config.image_backend { writeln!( f, "{}", @@ -283,7 +315,8 @@ impl std::fmt::Display for Info { panic!("No image backend found") } } else { - let mut logo_lines = AsciiArt::new(self.get_ascii(), self.colors(), self.bold_enabled); + let mut logo_lines = + AsciiArt::new(self.get_ascii(), self.colors(), self.config.no_bold); loop { match (logo_lines.next(), info_lines.next()) { (Some(logo_line), Some(info_line)) => { @@ -312,24 +345,12 @@ impl std::fmt::Display for Info { impl Info { #[tokio::main] - pub async fn new( - dir: &str, - logo: Language, - colors: Vec, - disabled: InfoFieldOn, - bold_flag: bool, - custom_image: Option, - image_backend: Option>, - no_merges: bool, - color_blocks_flag: bool, - author_nb: usize, - ignored_directories: Vec<&str>, - ) -> Result { - let repo = Repository::discover(&dir).map_err(|_| Error::NotGitRepo)?; + pub async fn new(config: Options) -> Result { + let repo = Repository::discover(&config.path).map_err(|_| Error::NotGitRepo)?; let workdir = repo.workdir().ok_or(Error::BareGitRepo)?; let workdir_str = workdir.to_str().unwrap(); let (languages_stats, number_of_lines) = - Language::get_language_stats(workdir_str, ignored_directories)?; + Language::get_language_stats(workdir_str, &config.excluded)?; let ( (repository_name, repository_url), @@ -344,7 +365,7 @@ impl Info { dominant_language, ) = futures::join!( Info::get_repo_name_and_url(&repo), - Info::get_git_history(workdir_str, no_merges), + Info::get_git_history(workdir_str, config.no_merges), Info::get_number_of_tags_branches(workdir_str), Info::get_current_commit_info(&repo), Info::get_git_version_and_username(workdir_str), @@ -357,7 +378,7 @@ impl Info { let creation_date = Info::get_creation_date(&git_history); let number_of_commits = Info::get_number_of_commits(&git_history); - let authors = Info::get_authors(&git_history, author_nb); + let authors = Info::get_authors(&git_history, config.number_of_authors); let last_change = Info::get_date_of_last_commit(&git_history); Ok(Info { @@ -379,13 +400,7 @@ impl Info { number_of_tags, number_of_branches, license: project_license?, - custom_logo: logo, - custom_colors: colors, - disable_fields: disabled, - bold_enabled: bold_flag, - no_color_blocks: color_blocks_flag, - custom_image, - image_backend, + config, }) } @@ -737,20 +752,20 @@ impl Info { } fn get_ascii(&self) -> &str { - let language = if let Language::Unknown = self.custom_logo { + let language = if let Language::Unknown = self.config.ascii_language { &self.dominant_language } else { - &self.custom_logo + &self.config.ascii_language }; language.get_ascii_art() } fn colors(&self) -> Vec { - let language = if let Language::Unknown = self.custom_logo { + let language = if let Language::Unknown = self.config.ascii_language { &self.dominant_language } else { - &self.custom_logo + &self.config.ascii_language }; let colors = language.get_colors(); @@ -759,7 +774,7 @@ impl Info { .iter() .enumerate() .map(|(index, default_color)| { - if let Some(color_num) = self.custom_colors.get(index) { + if let Some(color_num) = self.config.ascii_colors.get(index) { if let Some(color) = Info::num_to_color(color_num) { return color; } @@ -796,7 +811,7 @@ impl Info { /// Returns a formatted info label with the desired color and boldness fn get_formatted_info_label(&self, label: &str, color: Color) -> ColoredString { let mut formatted_label = label.color(color); - if self.bold_enabled { + if self.config.no_bold { formatted_label = formatted_label.bold(); } formatted_label diff --git a/src/language.rs b/src/language.rs index 4b187b407..5c54b60a8 100644 --- a/src/language.rs +++ b/src/language.rs @@ -181,7 +181,7 @@ impl Language { pub fn get_language_stats( dir: &str, - ignored_directories: Vec<&str>, + ignored_directories: &[String], ) -> Result<(Vec<(Language, f64)>, usize)> { let tokei_langs = project_languages(&dir, ignored_directories); let languages_stat = @@ -205,7 +205,7 @@ fn get_total_loc(languages: &tokei::Languages) -> usize { .fold(0, |sum, val| sum + val.code) } -fn project_languages(dir: &str, ignored_directories: Vec<&str>) -> tokei::Languages { +fn project_languages(dir: &str, ignored_directories: &[String]) -> tokei::Languages { use tokei::Config; let mut languages = tokei::Languages::new(); @@ -219,7 +219,7 @@ fn project_languages(dir: &str, ignored_directories: Vec<&str>) -> tokei::Langua let re = Regex::new(r"((.*)+/)+(.*)").unwrap(); let mut v = Vec::with_capacity(ignored_directories.len()); for ignored in ignored_directories { - if re.is_match(ignored) { + if re.is_match(&ignored) { let p = if ignored.starts_with('/') { "**" } else { @@ -233,7 +233,8 @@ fn project_languages(dir: &str, ignored_directories: Vec<&str>) -> tokei::Langua let ignored_directories_for_ab: Vec<&str> = v.iter().map(|x| &**x).collect(); languages.get_statistics(&[&dir], &ignored_directories_for_ab, &tokei_config); } else { - languages.get_statistics(&[&dir], &ignored_directories, &tokei_config); + let ignored_directories_ref: Vec<&str> = ignored_directories.iter().map(|s| &**s).collect(); + languages.get_statistics(&[&dir], &ignored_directories_ref, &tokei_config); } languages diff --git a/src/main.rs b/src/main.rs index 4d38aefe6..abe153820 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,70 +5,30 @@ extern crate clap; use image_backends::ImageBackend; use { ascii_art::AsciiArt, - clap::{App, Arg}, - colored::*, commit_info::CommitInfo, error::Error, - info::Info, + exit_codes::ExitCode, + info::{Info, InfoFieldOn, InfoFields}, language::Language, - std::{ - convert::From, - process::{Command, Stdio}, - result, - str::FromStr, - }, - strum::{EnumCount, EnumIter, EnumString, IntoEnumIterator, IntoStaticStr}, + process::{Command, Stdio}, + std::{convert::From, env, process, result, str::FromStr}, + strum::IntoEnumIterator, }; +mod app; mod ascii_art; mod commit_info; mod error; +mod exit_codes; mod image_backends; mod info; mod language; mod license; +mod options; type Result = result::Result; -#[derive(Default)] -pub struct InfoFieldOn { - git_info: bool, - project: bool, - head: bool, - version: bool, - created: bool, - languages: bool, - authors: bool, - last_change: bool, - repo: bool, - commits: bool, - pending: bool, - lines_of_code: bool, - size: bool, - license: bool, -} - -#[derive(PartialEq, Eq, EnumString, EnumCount, EnumIter, IntoStaticStr)] -#[strum(serialize_all = "snake_case")] -enum InfoFields { - GitInfo, - Project, - HEAD, - Version, - Created, - Languages, - Authors, - LastChange, - Repo, - Commits, - Pending, - LinesOfCode, - Size, - License, - UnrecognizedField, -} - -fn main() -> Result<()> { +fn run() -> Result<()> { #[cfg(target_os = "windows")] let enabled = ansi_term::enable_ansi_support().is_ok(); @@ -83,131 +43,10 @@ fn main() -> Result<()> { return Err(Error::GitNotInstalled); } - let possible_languages: Vec = Language::iter() - .filter(|language| *language != Language::Unknown) - .map(|language| language.to_string().to_lowercase()) - .collect(); + let matches = app::build_app().get_matches_from(env::args_os()); - #[cfg(target_os = "linux")] - let possible_backends = ["kitty", "sixel"]; - #[cfg(not(target_os = "linux"))] - let possible_backends = []; - - let matches = App::new(crate_name!()) - .version(crate_version!()) - .author("o2sh ") - .about(crate_description!()) - .arg(Arg::with_name("input").default_value(".").help( - "Run as if onefetch was started in instead of the current working directory.", - )) - .arg( - Arg::with_name("ascii-language") - .short("a") - .long("ascii-language") - .takes_value(true) - .possible_values( - &possible_languages - .iter() - .map(|l| l.as_str()) - .collect::>(), - ) - .case_insensitive(true) - .help("Which language's ascii art to print."), - ) - .arg( - Arg::with_name("disable-fields") - .long("disable-fields") - .short("d") - .multiple(true) - .takes_value(true) - .case_insensitive(true) - .help("Allows you to disable an info line from appearing in the output.") - .possible_values( - &InfoFields::iter() - .take(InfoFields::COUNT - 1) - .map(|field| field.into()) - .collect::>() - .as_slice(), - ), - ) - .arg( - Arg::with_name("ascii-colors") - .short("c") - .long("ascii-colors") - .multiple(true) - .takes_value(true) - .possible_values(&[ - "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", - "15", - ]) - .hide_possible_values(true) - .help(&format!( - "Colors to print the ascii art. Possible values: [{0}{1}{2}{3}{4}{5}{6}{7}]", - "0".black(), - "1".red(), - "2".green(), - "3".yellow(), - "4".blue(), - "5".magenta(), - "6".cyan(), - "7".white() - )), - ) - .arg( - Arg::with_name("no-bold") - .long("no-bold") - .help("Turns off bold formatting."), - ) - .arg( - Arg::with_name("languages") - .short("l") - .long("languages") - .help("Prints out supported languages"), - ) - .arg( - Arg::with_name("image") - .short("i") - .long("image") - .takes_value(true) - .help("Which image to use. Possible values: [/path/to/img]"), - ) - .arg( - Arg::with_name("image-backend") - .long("image-backend") - .takes_value(true) - .possible_values(&possible_backends) - .help("Which image backend to use."), - ) - .arg( - Arg::with_name("no-merge-commits") - .long("no-merge-commits") - .help("Ignores merge commits"), - ) - .arg( - Arg::with_name("no-color-blocks") - .long("no-color-blocks") - .help("Hides the color blocks"), - ) - .arg( - Arg::with_name("authors-number") - .short("A") - .long("authors-number") - .takes_value(true) - .default_value("3") - .help("Number of authors to be shown."), - ) - .arg( - Arg::with_name("exclude") - .short("e") - .long("exclude") - .multiple(true) - .takes_value(true) - .help("Ignore all files & directories matching the pattern."), - ) - .get_matches(); - - let ignored_directories: Vec<&str> = if let Some(user_ignored) = matches.values_of("exclude") { - user_ignored.map(|s| s as &str).collect() + let excluded: Vec = if let Some(user_ignored) = matches.values_of("exclude") { + user_ignored.map(String::from).collect() } else { Vec::new() }; @@ -221,14 +60,15 @@ fn main() -> Result<()> { std::process::exit(0); } - let dir = String::from(matches.value_of("input").unwrap()); + let path = String::from(matches.value_of("input").unwrap()); - let custom_logo: Language = if let Some(ascii_language) = matches.value_of("ascii-language") { + let ascii_language: Language = if let Some(ascii_language) = matches.value_of("ascii-language") + { Language::from_str(&ascii_language.to_lowercase()).unwrap() } else { Language::Unknown }; - let mut disable_fields = InfoFieldOn { + let mut disabled_fields = InfoFieldOn { ..Default::default() }; @@ -243,39 +83,39 @@ fn main() -> Result<()> { .unwrap_or(InfoFields::UnrecognizedField); match item { - InfoFields::GitInfo => disable_fields.git_info = true, - InfoFields::Project => disable_fields.project = true, - InfoFields::HEAD => disable_fields.head = true, - InfoFields::Version => disable_fields.version = true, - InfoFields::Created => disable_fields.created = true, - InfoFields::Languages => disable_fields.languages = true, - InfoFields::Authors => disable_fields.authors = true, - InfoFields::LastChange => disable_fields.last_change = true, - InfoFields::Repo => disable_fields.repo = true, - InfoFields::Pending => disable_fields.pending = true, - InfoFields::Commits => disable_fields.commits = true, - InfoFields::LinesOfCode => disable_fields.lines_of_code = true, - InfoFields::Size => disable_fields.size = true, - InfoFields::License => disable_fields.license = true, + InfoFields::GitInfo => disabled_fields.git_info = true, + InfoFields::Project => disabled_fields.project = true, + InfoFields::HEAD => disabled_fields.head = true, + InfoFields::Version => disabled_fields.version = true, + InfoFields::Created => disabled_fields.created = true, + InfoFields::Languages => disabled_fields.languages = true, + InfoFields::Authors => disabled_fields.authors = true, + InfoFields::LastChange => disabled_fields.last_change = true, + InfoFields::Repo => disabled_fields.repo = true, + InfoFields::Pending => disabled_fields.pending = true, + InfoFields::Commits => disabled_fields.commits = true, + InfoFields::LinesOfCode => disabled_fields.lines_of_code = true, + InfoFields::Size => disabled_fields.size = true, + InfoFields::License => disabled_fields.license = true, _ => (), } } - let custom_colors: Vec = if let Some(values) = matches.values_of("ascii-colors") { + let ascii_colors: Vec = if let Some(values) = matches.values_of("ascii-colors") { values.map(String::from).collect() } else { Vec::new() }; - let bold_flag = !matches.is_present("no-bold"); + let no_bold = !matches.is_present("no-bold"); - let custom_image = if let Some(image_path) = matches.value_of("image") { + let image = if let Some(image_path) = matches.value_of("image") { Some(image::open(image_path).map_err(|_| Error::ImageLoadError)?) } else { None }; - let image_backend = if custom_image.is_some() { + let image_backend = if image.is_some() { if let Some(backend_name) = matches.value_of("image-backend") { #[cfg(target_os = "linux")] let backend = @@ -298,32 +138,46 @@ fn main() -> Result<()> { let no_merges = matches.is_present("no-merge-commits"); - let color_blocks_flag = matches.is_present("no-color-blocks"); + let no_color_blocks = matches.is_present("no-color-blocks"); - let author_number: usize = if let Some(value) = matches.value_of("authors-number") { + let number_of_authors: usize = if let Some(value) = matches.value_of("authors-number") { usize::from_str(value).unwrap() } else { 3 }; - let info = Info::new( - &dir, - custom_logo, - custom_colors, - disable_fields, - bold_flag, - custom_image, + let config = options::Options { + path, + ascii_language, + ascii_colors, + disabled_fields, + no_bold, + image, image_backend, no_merges, - color_blocks_flag, - author_number, - ignored_directories, - )?; + no_color_blocks, + number_of_authors, + excluded, + }; + + let info = Info::new(config)?; print!("{}", info); Ok(()) } +fn main() { + let result = run(); + match result { + Ok(_) => { + process::exit(ExitCode::Success.into()); + } + Err(_) => { + process::exit(ExitCode::GeneralError.into()); + } + } +} + fn is_git_installed() -> bool { Command::new("git") .arg("--version") diff --git a/src/options.rs b/src/options.rs new file mode 100644 index 000000000..480f80a1e --- /dev/null +++ b/src/options.rs @@ -0,0 +1,30 @@ +use { + crate::{image_backends::ImageBackend, info::InfoFieldOn, language::Language}, + image::DynamicImage, +}; + +/// Configuration options for *onefetch*. +pub struct Options { + /// + pub path: String, + /// + pub ascii_language: Language, + /// + pub ascii_colors: Vec, + /// + pub disabled_fields: InfoFieldOn, + /// + pub no_bold: bool, + /// + pub image: Option, + /// + pub image_backend: Option>, + /// + pub no_merges: bool, + /// + pub no_color_blocks: bool, + /// + pub number_of_authors: usize, + /// + pub excluded: Vec, +} From 038b3c2d501a25604354bca5b336f2ac553a60ba Mon Sep 17 00:00:00 2001 From: Ossama Hjaji Date: Sat, 10 Oct 2020 01:07:04 +0200 Subject: [PATCH 02/14] add possible values for ascii_colors --- src/app.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index 93bd629b8..8d6ba8a27 100644 --- a/src/app.rs +++ b/src/app.rs @@ -52,7 +52,7 @@ pub fn build_app() -> App<'static, 'static> { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", ]) - .hide_possible_values(true), + .help("Colors to print the ascii art."), ) .arg( Arg::with_name("no-bold") From 71787c3883e33d44ce46d194b4eac6c3832e1f46 Mon Sep 17 00:00:00 2001 From: Ossama Hjaji Date: Sat, 10 Oct 2020 01:25:00 +0200 Subject: [PATCH 03/14] refacto declaration of option struct --- src/main.rs | 66 ++++++++++++++++++++--------------------------------- 1 file changed, 25 insertions(+), 41 deletions(-) diff --git a/src/main.rs b/src/main.rs index abe153820..cba23b827 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,12 +45,6 @@ fn run() -> Result<()> { let matches = app::build_app().get_matches_from(env::args_os()); - let excluded: Vec = if let Some(user_ignored) = matches.values_of("exclude") { - user_ignored.map(String::from).collect() - } else { - Vec::new() - }; - if matches.is_present("languages") { let iterator = Language::iter().filter(|x| *x != Language::Unknown); @@ -60,14 +54,6 @@ fn run() -> Result<()> { std::process::exit(0); } - let path = String::from(matches.value_of("input").unwrap()); - - let ascii_language: Language = if let Some(ascii_language) = matches.value_of("ascii-language") - { - Language::from_str(&ascii_language.to_lowercase()).unwrap() - } else { - Language::Unknown - }; let mut disabled_fields = InfoFieldOn { ..Default::default() }; @@ -101,14 +87,6 @@ fn run() -> Result<()> { } } - let ascii_colors: Vec = if let Some(values) = matches.values_of("ascii-colors") { - values.map(String::from).collect() - } else { - Vec::new() - }; - - let no_bold = !matches.is_present("no-bold"); - let image = if let Some(image_path) = matches.value_of("image") { Some(image::open(image_path).map_err(|_| Error::ImageLoadError)?) } else { @@ -136,28 +114,34 @@ fn run() -> Result<()> { None }; - let no_merges = matches.is_present("no-merge-commits"); - - let no_color_blocks = matches.is_present("no-color-blocks"); - - let number_of_authors: usize = if let Some(value) = matches.value_of("authors-number") { - usize::from_str(value).unwrap() - } else { - 3 - }; - let config = options::Options { - path, - ascii_language, - ascii_colors, + path: String::from(matches.value_of("input").unwrap()), + ascii_language: if let Some(ascii_language) = matches.value_of("ascii-language") { + Language::from_str(&ascii_language.to_lowercase()).unwrap() + } else { + Language::Unknown + }, + ascii_colors: if let Some(values) = matches.values_of("ascii-colors") { + values.map(String::from).collect() + } else { + Vec::new() + }, disabled_fields, - no_bold, - image, + no_bold: !matches.is_present("no-bold"), + image: image, image_backend, - no_merges, - no_color_blocks, - number_of_authors, - excluded, + no_merges: matches.is_present("no-merge-commits"), + no_color_blocks: matches.is_present("no-color-blocks"), + number_of_authors: if let Some(value) = matches.value_of("authors-number") { + usize::from_str(value).unwrap() + } else { + 3 + }, + excluded: if let Some(user_ignored) = matches.values_of("exclude") { + user_ignored.map(String::from).collect() + } else { + Vec::new() + }, }; let info = Info::new(config)?; From 2b9f425bdaa5556d72909119c9b70bf6e1d4dd12 Mon Sep 17 00:00:00 2001 From: Ossama Hjaji Date: Sat, 10 Oct 2020 14:24:15 +0200 Subject: [PATCH 04/14] further refactoring of main.rs --- src/{app.rs => clap_app.rs} | 0 src/image_backends/mod.rs | 31 +++++++++-- src/info.rs | 33 +++++++++++- src/main.rs | 100 +++++++++++------------------------- 4 files changed, 89 insertions(+), 75 deletions(-) rename src/{app.rs => clap_app.rs} (100%) diff --git a/src/app.rs b/src/clap_app.rs similarity index 100% rename from src/app.rs rename to src/clap_app.rs diff --git a/src/image_backends/mod.rs b/src/image_backends/mod.rs index 7d769c766..da5bc762f 100644 --- a/src/image_backends/mod.rs +++ b/src/image_backends/mod.rs @@ -1,15 +1,15 @@ use image::DynamicImage; -#[cfg(target_os = "linux")] +#[cfg(unix)] pub mod kitty; -#[cfg(target_os = "linux")] +#[cfg(unix)] pub mod sixel; pub trait ImageBackend { fn add_image(&self, lines: Vec, image: &DynamicImage) -> String; } -#[cfg(target_os = "linux")] +#[cfg(unix)] pub fn get_best_backend() -> Option> { if kitty::KittyBackend::supported() { Some(Box::new(kitty::KittyBackend::new())) @@ -20,7 +20,30 @@ pub fn get_best_backend() -> Option> { } } -#[cfg(not(target_os = "linux"))] +pub fn get_image_backend( + image: &Option, + backend_name: &str, +) -> Option> { + if image.is_some() { + #[cfg(unix)] + let backend = Some(match backend_name { + "kitty" => { + Box::new(kitty::KittyBackend::new()) as Box + } + "sixel" => { + Box::new(sixel::SixelBackend::new()) as Box + } + _ => unreachable!(), + }); + #[cfg(not(unix))] + let backend = None; + backend + } else { + None + } +} + +#[cfg(not(unix))] pub fn get_best_backend() -> Option> { None } diff --git a/src/info.rs b/src/info.rs index f45e7931c..5b96d673a 100644 --- a/src/info.rs +++ b/src/info.rs @@ -8,7 +8,7 @@ use { colored::{Color, ColoredString, Colorize}, git2::Repository, regex::Regex, - std::{ffi::OsStr, fmt::Write, fs}, + std::{ffi::OsStr, fmt::Write, fs, str::FromStr}, strum::{EnumCount, EnumIter, EnumString, IntoStaticStr}, tokio::process::Command, }; @@ -77,6 +77,37 @@ pub struct Info { config: Options, } +pub fn get_disabled_fields(fields_to_hide: Vec) -> Result { + let mut disabled_fields = InfoFieldOn { + ..Default::default() + }; + + for field in fields_to_hide.iter() { + let item = InfoFields::from_str(field.to_lowercase().as_str()) + .unwrap_or(InfoFields::UnrecognizedField); + + match item { + InfoFields::GitInfo => disabled_fields.git_info = true, + InfoFields::Project => disabled_fields.project = true, + InfoFields::HEAD => disabled_fields.head = true, + InfoFields::Version => disabled_fields.version = true, + InfoFields::Created => disabled_fields.created = true, + InfoFields::Languages => disabled_fields.languages = true, + InfoFields::Authors => disabled_fields.authors = true, + InfoFields::LastChange => disabled_fields.last_change = true, + InfoFields::Repo => disabled_fields.repo = true, + InfoFields::Pending => disabled_fields.pending = true, + InfoFields::Commits => disabled_fields.commits = true, + InfoFields::LinesOfCode => disabled_fields.lines_of_code = true, + InfoFields::Size => disabled_fields.size = true, + InfoFields::License => disabled_fields.license = true, + _ => (), + } + } + + Ok(disabled_fields) +} + impl std::fmt::Display for Info { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let mut buf = String::new(); diff --git a/src/main.rs b/src/main.rs index cba23b827..41eb4a6c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,20 @@ #[macro_use] extern crate clap; -#[cfg(target_os = "linux")] -use image_backends::ImageBackend; +#[cfg(unix)] use { ascii_art::AsciiArt, commit_info::CommitInfo, error::Error, exit_codes::ExitCode, - info::{Info, InfoFieldOn, InfoFields}, + info::Info, language::Language, process::{Command, Stdio}, std::{convert::From, env, process, result, str::FromStr}, strum::IntoEnumIterator, }; -mod app; +mod clap_app; mod ascii_art; mod commit_info; mod error; @@ -28,90 +27,38 @@ mod options; type Result = result::Result; -fn run() -> Result<()> { - #[cfg(target_os = "windows")] - let enabled = ansi_term::enable_ansi_support().is_ok(); - - #[cfg(not(target_os = "windows"))] - let enabled = true; - - if enabled { - colored::control::set_override(true); - } +/// Returns `Err(..)` upon fatal errors. Otherwise, returns `Ok(true)` on full success and +/// `Ok(false)` if any intermediate errors occurred (were printed). +fn run() -> Result { + #[cfg(windows)] + let _ = ansi_term::enable_ansi_support(); if !is_git_installed() { return Err(Error::GitNotInstalled); } - let matches = app::build_app().get_matches_from(env::args_os()); + let matches = clap_app::build_app().get_matches_from(env::args_os()); if matches.is_present("languages") { - let iterator = Language::iter().filter(|x| *x != Language::Unknown); - - for l in iterator { - println!("{}", l); - } - std::process::exit(0); + return list_languages(); } - let mut disabled_fields = InfoFieldOn { - ..Default::default() - }; - let fields_to_hide: Vec = if let Some(values) = matches.values_of("disable-fields") { values.map(String::from).collect() } else { Vec::new() }; - for field in fields_to_hide.iter() { - let item = InfoFields::from_str(field.to_lowercase().as_str()) - .unwrap_or(InfoFields::UnrecognizedField); - - match item { - InfoFields::GitInfo => disabled_fields.git_info = true, - InfoFields::Project => disabled_fields.project = true, - InfoFields::HEAD => disabled_fields.head = true, - InfoFields::Version => disabled_fields.version = true, - InfoFields::Created => disabled_fields.created = true, - InfoFields::Languages => disabled_fields.languages = true, - InfoFields::Authors => disabled_fields.authors = true, - InfoFields::LastChange => disabled_fields.last_change = true, - InfoFields::Repo => disabled_fields.repo = true, - InfoFields::Pending => disabled_fields.pending = true, - InfoFields::Commits => disabled_fields.commits = true, - InfoFields::LinesOfCode => disabled_fields.lines_of_code = true, - InfoFields::Size => disabled_fields.size = true, - InfoFields::License => disabled_fields.license = true, - _ => (), - } - } - let image = if let Some(image_path) = matches.value_of("image") { Some(image::open(image_path).map_err(|_| Error::ImageLoadError)?) } else { None }; - let image_backend = if image.is_some() { - if let Some(backend_name) = matches.value_of("image-backend") { - #[cfg(target_os = "linux")] - let backend = - Some(match backend_name { - "kitty" => Box::new(image_backends::kitty::KittyBackend::new()) - as Box, - "sixel" => Box::new(image_backends::sixel::SixelBackend::new()) - as Box, - _ => unreachable!(), - }); - #[cfg(not(target_os = "linux"))] - let backend = None; - backend - } else { - crate::image_backends::get_best_backend() - } + let image_backend = if let Some(backend_name) = matches.value_of("image-backend") { + image_backends::get_image_backend(&image, backend_name) } else { - None + image_backends::get_best_backend() }; let config = options::Options { @@ -126,9 +73,9 @@ fn run() -> Result<()> { } else { Vec::new() }, - disabled_fields, + disabled_fields: info::get_disabled_fields(fields_to_hide)?, no_bold: !matches.is_present("no-bold"), - image: image, + image, image_backend, no_merges: matches.is_present("no-merge-commits"), no_color_blocks: matches.is_present("no-color-blocks"), @@ -147,15 +94,28 @@ fn run() -> Result<()> { let info = Info::new(config)?; print!("{}", info); - Ok(()) + Ok(true) +} + +pub fn list_languages() -> Result { + let iterator = Language::iter().filter(|x| *x != Language::Unknown); + + for l in iterator { + println!("{}", l); + } + + Ok(true) } fn main() { let result = run(); match result { - Ok(_) => { + Ok(true) => { process::exit(ExitCode::Success.into()); } + Ok(false) => { + process::exit(ExitCode::GeneralError.into()); + } Err(_) => { process::exit(ExitCode::GeneralError.into()); } From a2350af350b1280daed3adfc3e7217dabfd94403 Mon Sep 17 00:00:00 2001 From: Ossama Hjaji Date: Sat, 10 Oct 2020 14:35:25 +0200 Subject: [PATCH 05/14] try fix build --- src/image_backends/mod.rs | 12 ++++++------ src/main.rs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/image_backends/mod.rs b/src/image_backends/mod.rs index da5bc762f..feaee8039 100644 --- a/src/image_backends/mod.rs +++ b/src/image_backends/mod.rs @@ -1,15 +1,15 @@ use image::DynamicImage; -#[cfg(unix)] +#[cfg(target_os = "linux")] pub mod kitty; -#[cfg(unix)] +#[cfg(target_os = "linux")] pub mod sixel; pub trait ImageBackend { fn add_image(&self, lines: Vec, image: &DynamicImage) -> String; } -#[cfg(unix)] +#[cfg(target_os = "linux")] pub fn get_best_backend() -> Option> { if kitty::KittyBackend::supported() { Some(Box::new(kitty::KittyBackend::new())) @@ -25,7 +25,7 @@ pub fn get_image_backend( backend_name: &str, ) -> Option> { if image.is_some() { - #[cfg(unix)] + #[cfg(target_os = "linux")] let backend = Some(match backend_name { "kitty" => { Box::new(kitty::KittyBackend::new()) as Box @@ -35,7 +35,7 @@ pub fn get_image_backend( } _ => unreachable!(), }); - #[cfg(not(unix))] + #[cfg(not(target_os = "linux"))] let backend = None; backend } else { @@ -43,7 +43,7 @@ pub fn get_image_backend( } } -#[cfg(not(unix))] +#[cfg(not(target_os = "linux"))] pub fn get_best_backend() -> Option> { None } diff --git a/src/main.rs b/src/main.rs index 41eb4a6c1..9a18bf725 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate clap; -#[cfg(unix)] +#[cfg(target_os = "linux")] use { ascii_art::AsciiArt, commit_info::CommitInfo, From 24a36668790b8a052c403cd4ba9b42d8ea426051 Mon Sep 17 00:00:00 2001 From: Ossama Hjaji Date: Sat, 10 Oct 2020 16:12:17 +0200 Subject: [PATCH 06/14] fix error handling --- src/clap_app.rs | 2 +- src/error.rs | 3 ++- src/image_backends/mod.rs | 31 ++++++++++------------------- src/main.rs | 42 ++++++++++++++++++++------------------- 4 files changed, 35 insertions(+), 43 deletions(-) diff --git a/src/clap_app.rs b/src/clap_app.rs index 8d6ba8a27..55c1a071b 100644 --- a/src/clap_app.rs +++ b/src/clap_app.rs @@ -1,6 +1,6 @@ use { crate::info::InfoFields, - clap::{App, AppSettings, Arg}, + clap::{crate_description, crate_name, crate_version, App, AppSettings, Arg}, strum::{EnumCount, IntoEnumIterator}, }; diff --git a/src/error.rs b/src/error.rs index de371e923..29013e7e0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,5 @@ /// Custom error type +#[derive(Debug)] pub enum Error { /// Sourcecode could be located SourceCodeNotFound, @@ -20,7 +21,7 @@ pub enum Error { LicenseDetectorError, } -impl std::fmt::Debug for Error { +impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let content = match self { Error::SourceCodeNotFound => "Could not find any source code in this directory", diff --git a/src/image_backends/mod.rs b/src/image_backends/mod.rs index feaee8039..0fb37aa11 100644 --- a/src/image_backends/mod.rs +++ b/src/image_backends/mod.rs @@ -20,27 +20,16 @@ pub fn get_best_backend() -> Option> { } } -pub fn get_image_backend( - image: &Option, - backend_name: &str, -) -> Option> { - if image.is_some() { - #[cfg(target_os = "linux")] - let backend = Some(match backend_name { - "kitty" => { - Box::new(kitty::KittyBackend::new()) as Box - } - "sixel" => { - Box::new(sixel::SixelBackend::new()) as Box - } - _ => unreachable!(), - }); - #[cfg(not(target_os = "linux"))] - let backend = None; - backend - } else { - None - } +pub fn get_image_backend(backend_name: &str) -> Option> { + #[cfg(target_os = "linux")] + let backend = Some(match backend_name { + "kitty" => Box::new(kitty::KittyBackend::new()) as Box, + "sixel" => Box::new(sixel::SixelBackend::new()) as Box, + _ => unreachable!(), + }); + #[cfg(not(target_os = "linux"))] + let backend = None; + backend } #[cfg(not(target_os = "linux"))] diff --git a/src/main.rs b/src/main.rs index 9a18bf725..c8c897ec0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,18 @@ -#[macro_use] -extern crate clap; - -#[cfg(target_os = "linux")] use { ascii_art::AsciiArt, + colored::Colorize, commit_info::CommitInfo, error::Error, exit_codes::ExitCode, info::Info, language::Language, process::{Command, Stdio}, - std::{convert::From, env, process, result, str::FromStr}, + std::{convert::From, env, io::Write, process, result, str::FromStr}, strum::IntoEnumIterator, }; -mod clap_app; mod ascii_art; +mod clap_app; mod commit_info; mod error; mod exit_codes; @@ -27,9 +24,7 @@ mod options; type Result = result::Result; -/// Returns `Err(..)` upon fatal errors. Otherwise, returns `Ok(true)` on full success and -/// `Ok(false)` if any intermediate errors occurred (were printed). -fn run() -> Result { +fn run() -> Result<()> { #[cfg(windows)] let _ = ansi_term::enable_ansi_support(); @@ -55,10 +50,14 @@ fn run() -> Result { None }; - let image_backend = if let Some(backend_name) = matches.value_of("image-backend") { - image_backends::get_image_backend(&image, backend_name) + let image_backend = if image.is_some() { + if let Some(backend_name) = matches.value_of("image-backend") { + image_backends::get_image_backend(backend_name) + } else { + image_backends::get_best_backend() + } } else { - image_backends::get_best_backend() + None }; let config = options::Options { @@ -94,34 +93,37 @@ fn run() -> Result { let info = Info::new(config)?; print!("{}", info); - Ok(true) + Ok(()) } -pub fn list_languages() -> Result { +pub fn list_languages() -> Result<()> { let iterator = Language::iter().filter(|x| *x != Language::Unknown); for l in iterator { println!("{}", l); } - Ok(true) + Ok(()) } fn main() { let result = run(); match result { - Ok(true) => { + Ok(_) => { process::exit(ExitCode::Success.into()); } - Ok(false) => { - process::exit(ExitCode::GeneralError.into()); - } - Err(_) => { + Err(error) => { + let stderr = std::io::stderr(); + default_error_handler(&error, &mut stderr.lock()); process::exit(ExitCode::GeneralError.into()); } } } +pub fn default_error_handler(error: &Error, output: &mut dyn Write) { + writeln!(output, "{}: {}", "[onefetch error]".red(), error).ok(); +} + fn is_git_installed() -> bool { Command::new("git") .arg("--version") From 444f3b299842afa2b0c20b085c5aee7c51503108 Mon Sep 17 00:00:00 2001 From: o2sh Date: Sun, 11 Oct 2020 03:28:06 +0200 Subject: [PATCH 07/14] use error_chain for error handling --- Cargo.lock | 17 +++++++++++++++++ Cargo.toml | 1 + src/error.rs | 48 ++++++++++++----------------------------------- src/exit_codes.rs | 14 -------------- src/info.rs | 19 +++++++++---------- src/language.rs | 4 ++-- src/license.rs | 6 ++---- src/main.rs | 24 +++++++++--------------- 8 files changed, 52 insertions(+), 81 deletions(-) delete mode 100644 src/exit_codes.rs diff --git a/Cargo.lock b/Cargo.lock index 948b7779b..7e4297a54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -470,6 +470,16 @@ dependencies = [ "termcolor", ] +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "backtrace", + "version_check", +] + [[package]] name = "failure" version = "0.1.8" @@ -1150,6 +1160,7 @@ dependencies = [ "bytecount", "clap", "colored", + "error-chain", "futures", "git2", "image", @@ -1931,6 +1942,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + [[package]] name = "walkdir" version = "2.3.1" diff --git a/Cargo.toml b/Cargo.toml index 6ea2f6973..3187a3338 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ image = "0.23.10" regex = "1" futures = "0.3.6" tokio = { version = "0.2.22", features = ["full"] } +error-chain = "0.12" [target.'cfg(windows)'.dependencies] ansi_term = "0.12" diff --git a/src/error.rs b/src/error.rs index 29013e7e0..12b407aa6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,39 +1,15 @@ -/// Custom error type -#[derive(Debug)] -pub enum Error { - /// Sourcecode could be located - SourceCodeNotFound, - /// Git is not installed or did not function properly - GitNotInstalled, - /// Did not find any git data in the directory - NoGitData, - /// An IO error occoured while reading ./ - ReadDirectory, - /// Not in a Git Repo - NotGitRepo, - /// Error while getting branch info - BareGitRepo, - /// Repository is a bare git repo - ReferenceInfoError, - /// Image probably doesn't exist or has wrong format - ImageLoadError, - /// Could not initialize the license detector - LicenseDetectorError, -} +use colored::Colorize; +use error_chain::error_chain; +use std::io::Write; -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let content = match self { - Error::SourceCodeNotFound => "Could not find any source code in this directory", - Error::GitNotInstalled => "Git failed to execute", - Error::NoGitData => "Could not retrieve git configuration data", - Error::ReadDirectory => "Could not read directory", - Error::NotGitRepo => "Could not find a valid git repo on the current path", - Error::BareGitRepo => "Unable to run onefetch on bare git repos", - Error::ReferenceInfoError => "Error while retrieving reference information", - Error::ImageLoadError => "Could not load the specified image", - Error::LicenseDetectorError => "Could not initialize the license detector", - }; - write!(f, "{}", content) +error_chain! { + foreign_links { + Clap(::clap::Error) #[cfg(feature = "application")]; + Io(::std::io::Error); + ParseIntError(::std::num::ParseIntError); } } + +pub fn default_error_handler(error: &Error, output: &mut dyn Write) { + writeln!(output, "{}: {}", "[onefetch error]".red(), error).ok(); +} diff --git a/src/exit_codes.rs b/src/exit_codes.rs deleted file mode 100644 index 9c85d9ce8..000000000 --- a/src/exit_codes.rs +++ /dev/null @@ -1,14 +0,0 @@ -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum ExitCode { - Success, - GeneralError, -} - -impl Into for ExitCode { - fn into(self) -> i32 { - match self { - ExitCode::Success => 0, - ExitCode::GeneralError => 1, - } - } -} diff --git a/src/info.rs b/src/info.rs index 5b96d673a..ac74b468f 100644 --- a/src/info.rs +++ b/src/info.rs @@ -1,9 +1,10 @@ use { crate::{ + error::*, language::Language, license::Detector, options::Options, - {AsciiArt, CommitInfo, Error}, + {AsciiArt, CommitInfo}, }, colored::{Color, ColoredString, Colorize}, git2::Repository, @@ -13,8 +14,6 @@ use { tokio::process::Command, }; -type Result = std::result::Result; - const LICENSE_FILES: [&str; 3] = ["LICENSE", "LICENCE", "COPYING"]; #[derive(Default)] @@ -377,8 +376,8 @@ impl std::fmt::Display for Info { impl Info { #[tokio::main] pub async fn new(config: Options) -> Result { - let repo = Repository::discover(&config.path).map_err(|_| Error::NotGitRepo)?; - let workdir = repo.workdir().ok_or(Error::BareGitRepo)?; + let repo = Repository::discover(&config.path).chain_err(||"Could not find a valid git repo on the current path")?; + let workdir = repo.workdir().chain_err(||"Unable to run onefetch on bare git repo")?; let workdir_str = workdir.to_str().unwrap(); let (languages_stats, number_of_lines) = Language::get_language_stats(workdir_str, &config.excluded)?; @@ -486,7 +485,7 @@ impl Info { } async fn get_repo_name_and_url(repo: &Repository) -> (String, String) { - let config = repo.config().map_err(|_| Error::NoGitData); + let config = repo.config().chain_err(|| "Could not retrieve git configuration data"); let mut remote_url = String::new(); let mut repository_name = String::new(); @@ -519,9 +518,9 @@ impl Info { } async fn get_current_commit_info(repo: &Repository) -> Result { - let head = repo.head().map_err(|_| Error::ReferenceInfoError)?; - let head_oid = head.target().ok_or(Error::ReferenceInfoError)?; - let refs = repo.references().map_err(|_| Error::ReferenceInfoError)?; + let head = repo.head().chain_err(|| "Error while retrieving reference information")?; + let head_oid = head.target().ok_or("Error while retrieving reference information")?; + let refs = repo.references().chain_err(|| "Error while retrieving reference information")?; let refs_info = refs .filter_map(|reference| match reference { Ok(reference) => match (reference.target(), reference.shorthand()) { @@ -754,7 +753,7 @@ impl Info { let detector = Detector::new()?; let mut output = fs::read_dir(dir) - .map_err(|_| Error::ReadDirectory)? + .chain_err(|| "Could not read directory")? .filter_map(std::result::Result::ok) .map(|entry| entry.path()) .filter(|entry| { diff --git a/src/language.rs b/src/language.rs index 5c54b60a8..52a63729b 100644 --- a/src/language.rs +++ b/src/language.rs @@ -1,5 +1,5 @@ use { - crate::{Error, Result}, + crate::error::*, colored::Color, regex::Regex, std::collections::HashMap, @@ -185,7 +185,7 @@ impl Language { ) -> Result<(Vec<(Language, f64)>, usize)> { let tokei_langs = project_languages(&dir, ignored_directories); let languages_stat = - Language::get_languages_stat(&tokei_langs).ok_or(Error::SourceCodeNotFound)?; + Language::get_languages_stat(&tokei_langs).ok_or("ErrorKind::SourceCodeNotFound()")?; let mut stat_vec: Vec<(_, _)> = languages_stat.into_iter().collect(); stat_vec.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap().reverse()); let loc = get_total_loc(&tokei_langs); diff --git a/src/license.rs b/src/license.rs index 4ac712c95..cf8d4af93 100644 --- a/src/license.rs +++ b/src/license.rs @@ -1,8 +1,6 @@ use askalono::{Store, TextData}; -use crate::Error; - -type Result = std::result::Result; +use crate::error::*; static CACHE_DATA: &[u8] = include_bytes!("../resources/licenses/cache.bin.zstd"); const MIN_THRESHOLD: f32 = 0.8; @@ -15,7 +13,7 @@ impl Detector { pub fn new() -> Result { Store::from_cache(CACHE_DATA) .map(|store| Self { store }) - .map_err(|_| Error::LicenseDetectorError) + .map_err(|_| "Could not initialize the license detector".into()) } pub fn analyze(&self, text: &str) -> Option { diff --git a/src/main.rs b/src/main.rs index c8c897ec0..1bbe7d166 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,14 @@ +// `error_chain!` can recurse deeply +#![recursion_limit = "1024"] + use { ascii_art::AsciiArt, - colored::Colorize, commit_info::CommitInfo, - error::Error, - exit_codes::ExitCode, + error::*, info::Info, language::Language, process::{Command, Stdio}, - std::{convert::From, env, io::Write, process, result, str::FromStr}, + std::{convert::From, env, process, str::FromStr}, strum::IntoEnumIterator, }; @@ -15,21 +16,18 @@ mod ascii_art; mod clap_app; mod commit_info; mod error; -mod exit_codes; mod image_backends; mod info; mod language; mod license; mod options; -type Result = result::Result; - fn run() -> Result<()> { #[cfg(windows)] let _ = ansi_term::enable_ansi_support(); if !is_git_installed() { - return Err(Error::GitNotInstalled); + return Err("Git failed to execute!".into()); } let matches = clap_app::build_app().get_matches_from(env::args_os()); @@ -45,7 +43,7 @@ fn run() -> Result<()> { }; let image = if let Some(image_path) = matches.value_of("image") { - Some(image::open(image_path).map_err(|_| Error::ImageLoadError)?) + Some(image::open(image_path).chain_err(|| "Could not load the specified image")?) } else { None }; @@ -110,20 +108,16 @@ fn main() { let result = run(); match result { Ok(_) => { - process::exit(ExitCode::Success.into()); + process::exit(0); } Err(error) => { let stderr = std::io::stderr(); default_error_handler(&error, &mut stderr.lock()); - process::exit(ExitCode::GeneralError.into()); + process::exit(1); } } } -pub fn default_error_handler(error: &Error, output: &mut dyn Write) { - writeln!(output, "{}: {}", "[onefetch error]".red(), error).ok(); -} - fn is_git_installed() -> bool { Command::new("git") .arg("--version") From f9e86a0be402dfbb489e142a0b7509353abb42c0 Mon Sep 17 00:00:00 2001 From: o2sh Date: Sun, 11 Oct 2020 03:45:34 +0200 Subject: [PATCH 08/14] rustfmt --- src/error.rs | 8 ++++++-- src/info.rs | 23 +++++++++++++++++------ src/language.rs | 4 ++-- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/error.rs b/src/error.rs index 12b407aa6..667602cb8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -10,6 +10,10 @@ error_chain! { } } -pub fn default_error_handler(error: &Error, output: &mut dyn Write) { - writeln!(output, "{}: {}", "[onefetch error]".red(), error).ok(); +pub fn default_error_handler(e: &Error, output: &mut dyn Write) { + writeln!(output, "{}: {}", "[onefetch error]".red(), e).ok(); + + for e in e.iter().skip(1) { + writeln!(output, "caused by: {}", e).ok(); + } } diff --git a/src/info.rs b/src/info.rs index ac74b468f..e39ace881 100644 --- a/src/info.rs +++ b/src/info.rs @@ -376,8 +376,11 @@ impl std::fmt::Display for Info { impl Info { #[tokio::main] pub async fn new(config: Options) -> Result { - let repo = Repository::discover(&config.path).chain_err(||"Could not find a valid git repo on the current path")?; - let workdir = repo.workdir().chain_err(||"Unable to run onefetch on bare git repo")?; + let repo = Repository::discover(&config.path) + .chain_err(|| "Could not find a valid git repo on the current path")?; + let workdir = repo + .workdir() + .chain_err(|| "Unable to run onefetch on bare git repo")?; let workdir_str = workdir.to_str().unwrap(); let (languages_stats, number_of_lines) = Language::get_language_stats(workdir_str, &config.excluded)?; @@ -485,7 +488,9 @@ impl Info { } async fn get_repo_name_and_url(repo: &Repository) -> (String, String) { - let config = repo.config().chain_err(|| "Could not retrieve git configuration data"); + let config = repo + .config() + .chain_err(|| "Could not retrieve git configuration data"); let mut remote_url = String::new(); let mut repository_name = String::new(); @@ -518,9 +523,15 @@ impl Info { } async fn get_current_commit_info(repo: &Repository) -> Result { - let head = repo.head().chain_err(|| "Error while retrieving reference information")?; - let head_oid = head.target().ok_or("Error while retrieving reference information")?; - let refs = repo.references().chain_err(|| "Error while retrieving reference information")?; + let head = repo + .head() + .chain_err(|| "Error while retrieving reference information")?; + let head_oid = head + .target() + .ok_or("Error while retrieving reference information")?; + let refs = repo + .references() + .chain_err(|| "Error while retrieving reference information")?; let refs_info = refs .filter_map(|reference| match reference { Ok(reference) => match (reference.target(), reference.shorthand()) { diff --git a/src/language.rs b/src/language.rs index 52a63729b..06f878f3c 100644 --- a/src/language.rs +++ b/src/language.rs @@ -184,8 +184,8 @@ impl Language { ignored_directories: &[String], ) -> Result<(Vec<(Language, f64)>, usize)> { let tokei_langs = project_languages(&dir, ignored_directories); - let languages_stat = - Language::get_languages_stat(&tokei_langs).ok_or("ErrorKind::SourceCodeNotFound()")?; + let languages_stat = Language::get_languages_stat(&tokei_langs) + .ok_or("Could not find any source code in this directory")?; let mut stat_vec: Vec<(_, _)> = languages_stat.into_iter().collect(); stat_vec.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap().reverse()); let loc = get_total_loc(&tokei_langs); From 7fd3b565462799555f1771f0df47304aceee11f7 Mon Sep 17 00:00:00 2001 From: o2sh Date: Sun, 11 Oct 2020 14:12:34 +0200 Subject: [PATCH 09/14] rearrange files --- src/main.rs | 19 ++++--------------- src/{ => onefetch}/ascii_art.rs | 0 src/{ => onefetch}/clap_app.rs | 0 src/{ => onefetch}/commit_info.rs | 0 src/{ => onefetch}/error.rs | 0 src/{ => onefetch}/image_backends/kitty.rs | 0 src/{ => onefetch}/image_backends/mod.rs | 0 src/{ => onefetch}/image_backends/sixel.rs | 0 src/{ => onefetch}/info.rs | 9 +++------ src/{ => onefetch}/language.rs | 10 +++++----- src/{ => onefetch}/license.rs | 4 ++-- src/onefetch/mod.rs | 9 +++++++++ src/{ => onefetch}/options.rs | 2 +- 13 files changed, 24 insertions(+), 29 deletions(-) rename src/{ => onefetch}/ascii_art.rs (100%) rename src/{ => onefetch}/clap_app.rs (100%) rename src/{ => onefetch}/commit_info.rs (100%) rename src/{ => onefetch}/error.rs (100%) rename src/{ => onefetch}/image_backends/kitty.rs (100%) rename src/{ => onefetch}/image_backends/mod.rs (100%) rename src/{ => onefetch}/image_backends/sixel.rs (100%) rename src/{ => onefetch}/info.rs (99%) rename src/{ => onefetch}/language.rs (97%) rename src/{ => onefetch}/license.rs (83%) create mode 100644 src/onefetch/mod.rs rename src/{ => onefetch}/options.rs (85%) diff --git a/src/main.rs b/src/main.rs index 1bbe7d166..d001a4d5b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,26 +1,15 @@ // `error_chain!` can recurse deeply #![recursion_limit = "1024"] +use crate::onefetch::{clap_app, error::*, image_backends, info, language::Language, options}; + use { - ascii_art::AsciiArt, - commit_info::CommitInfo, - error::*, - info::Info, - language::Language, process::{Command, Stdio}, std::{convert::From, env, process, str::FromStr}, strum::IntoEnumIterator, }; -mod ascii_art; -mod clap_app; -mod commit_info; -mod error; -mod image_backends; -mod info; -mod language; -mod license; -mod options; +mod onefetch; fn run() -> Result<()> { #[cfg(windows)] @@ -88,7 +77,7 @@ fn run() -> Result<()> { }, }; - let info = Info::new(config)?; + let info = info::Info::new(config)?; print!("{}", info); Ok(()) diff --git a/src/ascii_art.rs b/src/onefetch/ascii_art.rs similarity index 100% rename from src/ascii_art.rs rename to src/onefetch/ascii_art.rs diff --git a/src/clap_app.rs b/src/onefetch/clap_app.rs similarity index 100% rename from src/clap_app.rs rename to src/onefetch/clap_app.rs diff --git a/src/commit_info.rs b/src/onefetch/commit_info.rs similarity index 100% rename from src/commit_info.rs rename to src/onefetch/commit_info.rs diff --git a/src/error.rs b/src/onefetch/error.rs similarity index 100% rename from src/error.rs rename to src/onefetch/error.rs diff --git a/src/image_backends/kitty.rs b/src/onefetch/image_backends/kitty.rs similarity index 100% rename from src/image_backends/kitty.rs rename to src/onefetch/image_backends/kitty.rs diff --git a/src/image_backends/mod.rs b/src/onefetch/image_backends/mod.rs similarity index 100% rename from src/image_backends/mod.rs rename to src/onefetch/image_backends/mod.rs diff --git a/src/image_backends/sixel.rs b/src/onefetch/image_backends/sixel.rs similarity index 100% rename from src/image_backends/sixel.rs rename to src/onefetch/image_backends/sixel.rs diff --git a/src/info.rs b/src/onefetch/info.rs similarity index 99% rename from src/info.rs rename to src/onefetch/info.rs index e39ace881..ccb507360 100644 --- a/src/info.rs +++ b/src/onefetch/info.rs @@ -1,10 +1,7 @@ use { - crate::{ - error::*, - language::Language, - license::Detector, - options::Options, - {AsciiArt, CommitInfo}, + crate::onefetch::{ + ascii_art::AsciiArt, commit_info::CommitInfo, error::*, language::Language, + license::Detector, options::Options, }, colored::{Color, ColoredString, Colorize}, git2::Repository, diff --git a/src/language.rs b/src/onefetch/language.rs similarity index 97% rename from src/language.rs rename to src/onefetch/language.rs index 06f878f3c..28f1133b4 100644 --- a/src/language.rs +++ b/src/onefetch/language.rs @@ -1,5 +1,5 @@ use { - crate::error::*, + crate::onefetch::error::*, colored::Color, regex::Regex, std::collections::HashMap, @@ -40,8 +40,8 @@ macro_rules! define_languages { impl Language { pub fn get_ascii_art(&self) -> &str { match *self { - $( Language::$name => include_str!(concat!("../resources/", $ascii)), )* - Language::Unknown => include_str!("../resources/unknown.ascii"), + $( Language::$name => include_str!(concat!("../../resources/", $ascii)), )* + Language::Unknown => include_str!("../../resources/unknown.ascii"), } } @@ -78,7 +78,7 @@ macro_rules! define_languages { #[test] #[ignore] fn [<$name:lower _width>] () { - const ASCII: &str = include_str!(concat!("../resources/", $ascii)); + const ASCII: &str = include_str!(concat!("../../resources/", $ascii)); for (line_number, line) in ASCII.lines().enumerate() { let line = COLOR_INTERPOLATION.replace_all(line, ""); @@ -91,7 +91,7 @@ macro_rules! define_languages { #[test] #[ignore] fn [<$name:lower _height>] () { - const ASCII: &str = include_str!(concat!("../resources/", $ascii)); + const ASCII: &str = include_str!(concat!("../../resources/", $ascii)); assert_le!(ASCII.lines().count(), MAX_HEIGHT, concat!($ascii, " is too tall.")); } } diff --git a/src/license.rs b/src/onefetch/license.rs similarity index 83% rename from src/license.rs rename to src/onefetch/license.rs index cf8d4af93..183c8743c 100644 --- a/src/license.rs +++ b/src/onefetch/license.rs @@ -1,8 +1,8 @@ use askalono::{Store, TextData}; -use crate::error::*; +use crate::onefetch::error::*; -static CACHE_DATA: &[u8] = include_bytes!("../resources/licenses/cache.bin.zstd"); +static CACHE_DATA: &[u8] = include_bytes!("../../resources/licenses/cache.bin.zstd"); const MIN_THRESHOLD: f32 = 0.8; pub struct Detector { diff --git a/src/onefetch/mod.rs b/src/onefetch/mod.rs new file mode 100644 index 000000000..39004d6a0 --- /dev/null +++ b/src/onefetch/mod.rs @@ -0,0 +1,9 @@ +pub mod ascii_art; +pub mod clap_app; +pub mod commit_info; +pub mod error; +pub mod image_backends; +pub mod info; +pub mod language; +pub mod license; +pub mod options; diff --git a/src/options.rs b/src/onefetch/options.rs similarity index 85% rename from src/options.rs rename to src/onefetch/options.rs index 480f80a1e..6e786e76e 100644 --- a/src/options.rs +++ b/src/onefetch/options.rs @@ -1,5 +1,5 @@ use { - crate::{image_backends::ImageBackend, info::InfoFieldOn, language::Language}, + crate::onefetch::{image_backends::ImageBackend, info::InfoFieldOn, language::Language}, image::DynamicImage, }; From 4d69c11a3d028c14adf27db89f5a4c94ec280bfd Mon Sep 17 00:00:00 2001 From: o2sh Date: Sun, 11 Oct 2020 16:56:11 +0200 Subject: [PATCH 10/14] fix possible values for -a flag --- src/main.rs | 2 +- src/onefetch/clap_app.rs | 38 ++++++++++++++++++++++++++++---------- src/onefetch/language.rs | 4 ++-- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/main.rs b/src/main.rs index d001a4d5b..0364589ce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ // `error_chain!` can recurse deeply #![recursion_limit = "1024"] -use crate::onefetch::{clap_app, error::*, image_backends, info, language::Language, options}; +use onefetch::{clap_app, error::*, image_backends, info, language::Language, options}; use { process::{Command, Stdio}, diff --git a/src/onefetch/clap_app.rs b/src/onefetch/clap_app.rs index 55c1a071b..a294a7a34 100644 --- a/src/onefetch/clap_app.rs +++ b/src/onefetch/clap_app.rs @@ -1,5 +1,5 @@ use { - crate::info::InfoFields, + crate::onefetch::{info::InfoFields, language::Language}, clap::{crate_description, crate_name, crate_version, App, AppSettings, Arg}, strum::{EnumCount, IntoEnumIterator}, }; @@ -15,44 +15,55 @@ pub fn build_app() -> App<'static, 'static> { .about(crate_description!()) .setting(AppSettings::ColoredHelp) .setting(AppSettings::DeriveDisplayOrder) - .arg(Arg::with_name("input").default_value(".").help( + .setting(AppSettings::UnifiedHelpMessage) + .setting(AppSettings::HidePossibleValuesInHelp) + .arg(Arg::with_name("input").default_value(".").hide_default_value(true).help( "Run as if onefetch was started in instead of the current working directory.", )) .arg( Arg::with_name("ascii-language") .short("a") + .value_name("LANGUAGE") .long("ascii-language") + .max_values(1) .takes_value(true) .case_insensitive(true) - .help("Which language's ascii art to print."), + .help("Which LANGUAGE's ascii art to print.") + .possible_values( + &Language::iter() + .filter(|language| *language != Language::Unknown) + .map(|language| language.into()) + .collect::>() + ), ) .arg( Arg::with_name("disable-fields") .long("disable-fields") .short("d") + .value_name("FIELD") .multiple(true) .takes_value(true) .case_insensitive(true) - .help("Allows you to disable an info line from appearing in the output.") + .help("Allows you to disable FIELD(s) from appearing in the output.") .possible_values( &InfoFields::iter() .take(InfoFields::COUNT - 1) .map(|field| field.into()) .collect::>() - .as_slice(), ), ) .arg( Arg::with_name("ascii-colors") .short("c") .long("ascii-colors") + .value_name("X") .multiple(true) .takes_value(true) .possible_values(&[ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", ]) - .help("Colors to print the ascii art."), + .help("Colors (X X X...) to print the ascii art."), ) .arg( Arg::with_name("no-bold") @@ -69,15 +80,19 @@ pub fn build_app() -> App<'static, 'static> { Arg::with_name("image") .short("i") .long("image") + .value_name("IMAGE") .takes_value(true) - .help("Which image to use. Possible values: [/path/to/img]"), + .max_values(1) + .help("Path to the IMAGE file"), ) .arg( Arg::with_name("image-backend") .long("image-backend") + .value_name("BACKEND") .takes_value(true) + .max_values(1) .possible_values(&possible_backends) - .help("Which image backend to use."), + .help("Which image BACKEND to use."), ) .arg( Arg::with_name("no-merge-commits") @@ -93,16 +108,19 @@ pub fn build_app() -> App<'static, 'static> { Arg::with_name("authors-number") .short("A") .long("authors-number") + .value_name("NUM") .takes_value(true) + .max_values(1) .default_value("3") - .help("Number of authors to be shown."), + .help("NUM of authors to be shown."), ) .arg( Arg::with_name("exclude") .short("e") .long("exclude") + .value_name("EXCLUDE") .multiple(true) .takes_value(true) - .help("Ignore all files & directories matching the pattern."), + .help("Ignore all files & directories matching EXCLUDE."), ) } diff --git a/src/onefetch/language.rs b/src/onefetch/language.rs index 28f1133b4..70ad0f371 100644 --- a/src/onefetch/language.rs +++ b/src/onefetch/language.rs @@ -3,14 +3,14 @@ use { colored::Color, regex::Regex, std::collections::HashMap, - strum::{EnumIter, EnumString}, + strum::{EnumIter, EnumString, IntoStaticStr}, }; 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)] + #[derive(PartialEq, Eq, Hash, Clone, EnumString, EnumIter, IntoStaticStr)] pub enum Language { $( $( #[strum(serialize = $serialize)] )? From 5e595a5ddfea01ca850c58d8bb60eebaaa379d63 Mon Sep 17 00:00:00 2001 From: o2sh Date: Sun, 11 Oct 2020 20:39:10 +0200 Subject: [PATCH 11/14] extract info_fields into its own module --- src/main.rs | 6 ++- src/onefetch/clap_app.rs | 2 +- src/onefetch/info.rs | 82 ++++--------------------------------- src/onefetch/info_fields.rs | 74 +++++++++++++++++++++++++++++++++ src/onefetch/license.rs | 2 + src/onefetch/mod.rs | 1 + src/onefetch/options.rs | 2 +- 7 files changed, 90 insertions(+), 79 deletions(-) create mode 100644 src/onefetch/info_fields.rs diff --git a/src/main.rs b/src/main.rs index 0364589ce..f32bb0636 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,9 @@ // `error_chain!` can recurse deeply #![recursion_limit = "1024"] -use onefetch::{clap_app, error::*, image_backends, info, language::Language, options}; +use onefetch::{ + clap_app, error::*, image_backends, info, info_fields, language::Language, options, +}; use { process::{Command, Stdio}, @@ -59,7 +61,7 @@ fn run() -> Result<()> { } else { Vec::new() }, - disabled_fields: info::get_disabled_fields(fields_to_hide)?, + disabled_fields: info_fields::get_disabled_fields(fields_to_hide)?, no_bold: !matches.is_present("no-bold"), image, image_backend, diff --git a/src/onefetch/clap_app.rs b/src/onefetch/clap_app.rs index a294a7a34..cc072e04a 100644 --- a/src/onefetch/clap_app.rs +++ b/src/onefetch/clap_app.rs @@ -1,5 +1,5 @@ use { - crate::onefetch::{info::InfoFields, language::Language}, + crate::onefetch::{info_fields::InfoFields, language::Language}, clap::{crate_description, crate_name, crate_version, App, AppSettings, Arg}, strum::{EnumCount, IntoEnumIterator}, }; diff --git a/src/onefetch/info.rs b/src/onefetch/info.rs index ccb507360..7af6f4e55 100644 --- a/src/onefetch/info.rs +++ b/src/onefetch/info.rs @@ -1,56 +1,19 @@ use { crate::onefetch::{ - ascii_art::AsciiArt, commit_info::CommitInfo, error::*, language::Language, - license::Detector, options::Options, + ascii_art::AsciiArt, + commit_info::CommitInfo, + error::*, + language::Language, + license::{Detector, LICENSE_FILES}, + options::Options, }, colored::{Color, ColoredString, Colorize}, git2::Repository, regex::Regex, - std::{ffi::OsStr, fmt::Write, fs, str::FromStr}, - strum::{EnumCount, EnumIter, EnumString, IntoStaticStr}, + std::{ffi::OsStr, fmt::Write, fs}, tokio::process::Command, }; -const LICENSE_FILES: [&str; 3] = ["LICENSE", "LICENCE", "COPYING"]; - -#[derive(Default)] -pub struct InfoFieldOn { - pub git_info: bool, - pub project: bool, - pub head: bool, - pub version: bool, - pub created: bool, - pub languages: bool, - pub authors: bool, - pub last_change: bool, - pub repo: bool, - pub commits: bool, - pub pending: bool, - pub lines_of_code: bool, - pub size: bool, - pub license: bool, -} - -#[derive(PartialEq, Eq, EnumString, EnumCount, EnumIter, IntoStaticStr)] -#[strum(serialize_all = "snake_case")] -pub enum InfoFields { - GitInfo, - Project, - HEAD, - Version, - Created, - Languages, - Authors, - LastChange, - Repo, - Commits, - Pending, - LinesOfCode, - Size, - License, - UnrecognizedField, -} - pub struct Info { git_version: String, git_username: String, @@ -73,37 +36,6 @@ pub struct Info { config: Options, } -pub fn get_disabled_fields(fields_to_hide: Vec) -> Result { - let mut disabled_fields = InfoFieldOn { - ..Default::default() - }; - - for field in fields_to_hide.iter() { - let item = InfoFields::from_str(field.to_lowercase().as_str()) - .unwrap_or(InfoFields::UnrecognizedField); - - match item { - InfoFields::GitInfo => disabled_fields.git_info = true, - InfoFields::Project => disabled_fields.project = true, - InfoFields::HEAD => disabled_fields.head = true, - InfoFields::Version => disabled_fields.version = true, - InfoFields::Created => disabled_fields.created = true, - InfoFields::Languages => disabled_fields.languages = true, - InfoFields::Authors => disabled_fields.authors = true, - InfoFields::LastChange => disabled_fields.last_change = true, - InfoFields::Repo => disabled_fields.repo = true, - InfoFields::Pending => disabled_fields.pending = true, - InfoFields::Commits => disabled_fields.commits = true, - InfoFields::LinesOfCode => disabled_fields.lines_of_code = true, - InfoFields::Size => disabled_fields.size = true, - InfoFields::License => disabled_fields.license = true, - _ => (), - } - } - - Ok(disabled_fields) -} - impl std::fmt::Display for Info { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let mut buf = String::new(); diff --git a/src/onefetch/info_fields.rs b/src/onefetch/info_fields.rs new file mode 100644 index 000000000..2005e3ff0 --- /dev/null +++ b/src/onefetch/info_fields.rs @@ -0,0 +1,74 @@ +use { + crate::onefetch::error::*, + std::str::FromStr, + strum::{EnumCount, EnumIter, EnumString, IntoStaticStr}, +}; + +#[derive(Default)] +pub struct InfoFieldOn { + pub git_info: bool, + pub project: bool, + pub head: bool, + pub version: bool, + pub created: bool, + pub languages: bool, + pub authors: bool, + pub last_change: bool, + pub repo: bool, + pub commits: bool, + pub pending: bool, + pub lines_of_code: bool, + pub size: bool, + pub license: bool, +} + +#[derive(PartialEq, Eq, EnumString, EnumCount, EnumIter, IntoStaticStr)] +#[strum(serialize_all = "snake_case")] +pub enum InfoFields { + GitInfo, + Project, + HEAD, + Version, + Created, + Languages, + Authors, + LastChange, + Repo, + Commits, + Pending, + LinesOfCode, + Size, + License, + UnrecognizedField, +} + +pub fn get_disabled_fields(fields_to_hide: Vec) -> Result { + let mut disabled_fields = InfoFieldOn { + ..Default::default() + }; + + for field in fields_to_hide.iter() { + let item = InfoFields::from_str(field.to_lowercase().as_str()) + .unwrap_or(InfoFields::UnrecognizedField); + + match item { + InfoFields::GitInfo => disabled_fields.git_info = true, + InfoFields::Project => disabled_fields.project = true, + InfoFields::HEAD => disabled_fields.head = true, + InfoFields::Version => disabled_fields.version = true, + InfoFields::Created => disabled_fields.created = true, + InfoFields::Languages => disabled_fields.languages = true, + InfoFields::Authors => disabled_fields.authors = true, + InfoFields::LastChange => disabled_fields.last_change = true, + InfoFields::Repo => disabled_fields.repo = true, + InfoFields::Pending => disabled_fields.pending = true, + InfoFields::Commits => disabled_fields.commits = true, + InfoFields::LinesOfCode => disabled_fields.lines_of_code = true, + InfoFields::Size => disabled_fields.size = true, + InfoFields::License => disabled_fields.license = true, + _ => (), + } + } + + Ok(disabled_fields) +} diff --git a/src/onefetch/license.rs b/src/onefetch/license.rs index 183c8743c..4fb2b130f 100644 --- a/src/onefetch/license.rs +++ b/src/onefetch/license.rs @@ -2,6 +2,8 @@ use askalono::{Store, TextData}; use crate::onefetch::error::*; +pub const LICENSE_FILES: [&str; 3] = ["LICENSE", "LICENCE", "COPYING"]; + static CACHE_DATA: &[u8] = include_bytes!("../../resources/licenses/cache.bin.zstd"); const MIN_THRESHOLD: f32 = 0.8; diff --git a/src/onefetch/mod.rs b/src/onefetch/mod.rs index 39004d6a0..cddc36665 100644 --- a/src/onefetch/mod.rs +++ b/src/onefetch/mod.rs @@ -4,6 +4,7 @@ pub mod commit_info; pub mod error; pub mod image_backends; pub mod info; +pub mod info_fields; pub mod language; pub mod license; pub mod options; diff --git a/src/onefetch/options.rs b/src/onefetch/options.rs index 6e786e76e..9f378b59c 100644 --- a/src/onefetch/options.rs +++ b/src/onefetch/options.rs @@ -1,5 +1,5 @@ use { - crate::onefetch::{image_backends::ImageBackend, info::InfoFieldOn, language::Language}, + crate::onefetch::{image_backends::ImageBackend, info_fields::InfoFieldOn, language::Language}, image::DynamicImage, }; From 8419eddffe62c97fd3ed6d17b4a51dd55c890aee Mon Sep 17 00:00:00 2001 From: o2sh Date: Mon, 12 Oct 2020 14:39:14 +0200 Subject: [PATCH 12/14] merge Options with cli --- src/main.rs | 78 ++---------------------- src/onefetch/{clap_app.rs => cli.rs} | 90 +++++++++++++++++++++++++--- src/onefetch/info.rs | 2 +- src/onefetch/mod.rs | 3 +- src/onefetch/options.rs | 30 ---------- 5 files changed, 89 insertions(+), 114 deletions(-) rename src/onefetch/{clap_app.rs => cli.rs} (58%) delete mode 100644 src/onefetch/options.rs diff --git a/src/main.rs b/src/main.rs index f32bb0636..c1236a4cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,11 @@ // `error_chain!` can recurse deeply #![recursion_limit = "1024"] -use onefetch::{ - clap_app, error::*, image_backends, info, info_fields, language::Language, options, -}; +use onefetch::{cli::Options, error::*, info}; use { process::{Command, Stdio}, - std::{convert::From, env, process, str::FromStr}, - strum::IntoEnumIterator, + std::process, }; mod onefetch; @@ -21,80 +18,15 @@ fn run() -> Result<()> { return Err("Git failed to execute!".into()); } - let matches = clap_app::build_app().get_matches_from(env::args_os()); - - if matches.is_present("languages") { - return list_languages(); - } - - let fields_to_hide: Vec = if let Some(values) = matches.values_of("disable-fields") { - values.map(String::from).collect() - } else { - Vec::new() - }; - - let image = if let Some(image_path) = matches.value_of("image") { - Some(image::open(image_path).chain_err(|| "Could not load the specified image")?) - } else { - None - }; - - let image_backend = if image.is_some() { - if let Some(backend_name) = matches.value_of("image-backend") { - image_backends::get_image_backend(backend_name) - } else { - image_backends::get_best_backend() - } - } else { - None - }; - - let config = options::Options { - path: String::from(matches.value_of("input").unwrap()), - ascii_language: if let Some(ascii_language) = matches.value_of("ascii-language") { - Language::from_str(&ascii_language.to_lowercase()).unwrap() - } else { - Language::Unknown - }, - ascii_colors: if let Some(values) = matches.values_of("ascii-colors") { - values.map(String::from).collect() - } else { - Vec::new() - }, - disabled_fields: info_fields::get_disabled_fields(fields_to_hide)?, - no_bold: !matches.is_present("no-bold"), - image, - image_backend, - no_merges: matches.is_present("no-merge-commits"), - no_color_blocks: matches.is_present("no-color-blocks"), - number_of_authors: if let Some(value) = matches.value_of("authors-number") { - usize::from_str(value).unwrap() - } else { - 3 - }, - excluded: if let Some(user_ignored) = matches.values_of("exclude") { - user_ignored.map(String::from).collect() - } else { - Vec::new() - }, - }; + // Load command line options. + let options = Options::new()?; - let info = info::Info::new(config)?; + let info = info::Info::new(options)?; print!("{}", info); Ok(()) } -pub fn list_languages() -> Result<()> { - let iterator = Language::iter().filter(|x| *x != Language::Unknown); - - for l in iterator { - println!("{}", l); - } - - Ok(()) -} - fn main() { let result = run(); match result { diff --git a/src/onefetch/clap_app.rs b/src/onefetch/cli.rs similarity index 58% rename from src/onefetch/clap_app.rs rename to src/onefetch/cli.rs index cc072e04a..35c12fe44 100644 --- a/src/onefetch/clap_app.rs +++ b/src/onefetch/cli.rs @@ -1,16 +1,36 @@ use { - crate::onefetch::{info_fields::InfoFields, language::Language}, + crate::onefetch::{ + error::*, image_backends, info_fields, info_fields::InfoFields, language::Language, + }, clap::{crate_description, crate_name, crate_version, App, AppSettings, Arg}, + image::DynamicImage, + std::{convert::From, env, str::FromStr}, strum::{EnumCount, IntoEnumIterator}, }; -pub fn build_app() -> App<'static, 'static> { - #[cfg(target_os = "linux")] - let possible_backends = ["kitty", "sixel"]; - #[cfg(not(target_os = "linux"))] - let possible_backends = []; +pub struct Options { + pub path: String, + pub ascii_language: Language, + pub ascii_colors: Vec, + pub disabled_fields: info_fields::InfoFieldOn, + pub no_bold: bool, + pub image: Option, + pub image_backend: Option>, + pub no_merges: bool, + pub no_color_blocks: bool, + pub number_of_authors: usize, + pub excluded: Vec, +} + +impl Options { + /// Build `Options` from command line arguments. + pub fn new() -> Result { + #[cfg(target_os = "linux")] + let possible_backends = ["kitty", "sixel"]; + #[cfg(not(target_os = "linux"))] + let possible_backends = []; - App::new(crate_name!()) + let matches = App::new(crate_name!()) .version(crate_version!()) .about(crate_description!()) .setting(AppSettings::ColoredHelp) @@ -122,5 +142,59 @@ pub fn build_app() -> App<'static, 'static> { .multiple(true) .takes_value(true) .help("Ignore all files & directories matching EXCLUDE."), - ) + ).get_matches(); + + let fields_to_hide: Vec = if let Some(values) = matches.values_of("disable-fields") + { + values.map(String::from).collect() + } else { + Vec::new() + }; + + let image = if let Some(image_path) = matches.value_of("image") { + Some(image::open(image_path).chain_err(|| "Could not load the specified image")?) + } else { + None + }; + + let image_backend = if image.is_some() { + if let Some(backend_name) = matches.value_of("image-backend") { + image_backends::get_image_backend(backend_name) + } else { + image_backends::get_best_backend() + } + } else { + None + }; + + Ok(Options { + path: String::from(matches.value_of("input").unwrap()), + ascii_language: if let Some(ascii_language) = matches.value_of("ascii-language") { + Language::from_str(&ascii_language.to_lowercase()).unwrap() + } else { + Language::Unknown + }, + ascii_colors: if let Some(values) = matches.values_of("ascii-colors") { + values.map(String::from).collect() + } else { + Vec::new() + }, + disabled_fields: info_fields::get_disabled_fields(fields_to_hide)?, + no_bold: !matches.is_present("no-bold"), + image, + image_backend, + no_merges: matches.is_present("no-merge-commits"), + no_color_blocks: matches.is_present("no-color-blocks"), + number_of_authors: if let Some(value) = matches.value_of("authors-number") { + usize::from_str(value).unwrap() + } else { + 3 + }, + excluded: if let Some(user_ignored) = matches.values_of("exclude") { + user_ignored.map(String::from).collect() + } else { + Vec::new() + }, + }) + } } diff --git a/src/onefetch/info.rs b/src/onefetch/info.rs index 7af6f4e55..3ffa5ae16 100644 --- a/src/onefetch/info.rs +++ b/src/onefetch/info.rs @@ -1,11 +1,11 @@ use { crate::onefetch::{ ascii_art::AsciiArt, + cli::Options, commit_info::CommitInfo, error::*, language::Language, license::{Detector, LICENSE_FILES}, - options::Options, }, colored::{Color, ColoredString, Colorize}, git2::Repository, diff --git a/src/onefetch/mod.rs b/src/onefetch/mod.rs index cddc36665..8ac63b957 100644 --- a/src/onefetch/mod.rs +++ b/src/onefetch/mod.rs @@ -1,5 +1,5 @@ pub mod ascii_art; -pub mod clap_app; +pub mod cli; pub mod commit_info; pub mod error; pub mod image_backends; @@ -7,4 +7,3 @@ pub mod info; pub mod info_fields; pub mod language; pub mod license; -pub mod options; diff --git a/src/onefetch/options.rs b/src/onefetch/options.rs deleted file mode 100644 index 9f378b59c..000000000 --- a/src/onefetch/options.rs +++ /dev/null @@ -1,30 +0,0 @@ -use { - crate::onefetch::{image_backends::ImageBackend, info_fields::InfoFieldOn, language::Language}, - image::DynamicImage, -}; - -/// Configuration options for *onefetch*. -pub struct Options { - /// - pub path: String, - /// - pub ascii_language: Language, - /// - pub ascii_colors: Vec, - /// - pub disabled_fields: InfoFieldOn, - /// - pub no_bold: bool, - /// - pub image: Option, - /// - pub image_backend: Option>, - /// - pub no_merges: bool, - /// - pub no_color_blocks: bool, - /// - pub number_of_authors: usize, - /// - pub excluded: Vec, -} From be96551501d4db0324ee937aa827e7663ecaa532 Mon Sep 17 00:00:00 2001 From: o2sh Date: Mon, 12 Oct 2020 14:52:53 +0200 Subject: [PATCH 13/14] simplify no_bold logic --- src/onefetch/cli.rs | 2 +- src/onefetch/info.rs | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/onefetch/cli.rs b/src/onefetch/cli.rs index 35c12fe44..db2929450 100644 --- a/src/onefetch/cli.rs +++ b/src/onefetch/cli.rs @@ -180,7 +180,7 @@ impl Options { Vec::new() }, disabled_fields: info_fields::get_disabled_fields(fields_to_hide)?, - no_bold: !matches.is_present("no-bold"), + no_bold: matches.is_present("no-bold"), image, image_backend, no_merges: matches.is_present("no-merge-commits"), diff --git a/src/onefetch/info.rs b/src/onefetch/info.rs index 3ffa5ae16..bb1883118 100644 --- a/src/onefetch/info.rs +++ b/src/onefetch/info.rs @@ -275,7 +275,7 @@ impl std::fmt::Display for Info { } } else { let mut logo_lines = - AsciiArt::new(self.get_ascii(), self.colors(), self.config.no_bold); + AsciiArt::new(self.get_ascii(), self.colors(), !self.config.no_bold); loop { match (logo_lines.next(), info_lines.next()) { (Some(logo_line), Some(info_line)) => { @@ -780,11 +780,12 @@ impl Info { /// Returns a formatted info label with the desired color and boldness fn get_formatted_info_label(&self, label: &str, color: Color) -> ColoredString { - let mut formatted_label = label.color(color); + let formatted_label = label.color(color); if self.config.no_bold { - formatted_label = formatted_label.bold(); + formatted_label + } else { + formatted_label.bold() } - formatted_label } } From a8f345d4092e5f234e8b821d1b3322a2c6b5a00e Mon Sep 17 00:00:00 2001 From: o2sh Date: Mon, 12 Oct 2020 17:26:47 +0200 Subject: [PATCH 14/14] use CARGO_MANIFEST_DIR instead of relative path --- src/onefetch/cli.rs | 6 ------ src/onefetch/language.rs | 8 ++++---- src/onefetch/license.rs | 5 ++++- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/onefetch/cli.rs b/src/onefetch/cli.rs index db2929450..baae01daf 100644 --- a/src/onefetch/cli.rs +++ b/src/onefetch/cli.rs @@ -90,12 +90,6 @@ impl Options { .long("no-bold") .help("Turns off bold formatting."), ) - .arg( - Arg::with_name("languages") - .short("l") - .long("languages") - .help("Prints out supported languages"), - ) .arg( Arg::with_name("image") .short("i") diff --git a/src/onefetch/language.rs b/src/onefetch/language.rs index 70ad0f371..c9b32a9e8 100644 --- a/src/onefetch/language.rs +++ b/src/onefetch/language.rs @@ -40,8 +40,8 @@ macro_rules! define_languages { impl Language { pub fn get_ascii_art(&self) -> &str { match *self { - $( Language::$name => include_str!(concat!("../../resources/", $ascii)), )* - Language::Unknown => include_str!("../../resources/unknown.ascii"), + $( Language::$name => include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/resources/", $ascii)), )* + Language::Unknown => include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/resources/unknown.ascii")), } } @@ -78,7 +78,7 @@ macro_rules! define_languages { #[test] #[ignore] fn [<$name:lower _width>] () { - const ASCII: &str = include_str!(concat!("../../resources/", $ascii)); + const ASCII: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/resources/", $ascii)); for (line_number, line) in ASCII.lines().enumerate() { let line = COLOR_INTERPOLATION.replace_all(line, ""); @@ -91,7 +91,7 @@ macro_rules! define_languages { #[test] #[ignore] fn [<$name:lower _height>] () { - const ASCII: &str = include_str!(concat!("../../resources/", $ascii)); + const ASCII: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/resources/", $ascii)); assert_le!(ASCII.lines().count(), MAX_HEIGHT, concat!($ascii, " is too tall.")); } } diff --git a/src/onefetch/license.rs b/src/onefetch/license.rs index 4fb2b130f..98870457c 100644 --- a/src/onefetch/license.rs +++ b/src/onefetch/license.rs @@ -4,7 +4,10 @@ use crate::onefetch::error::*; pub const LICENSE_FILES: [&str; 3] = ["LICENSE", "LICENCE", "COPYING"]; -static CACHE_DATA: &[u8] = include_bytes!("../../resources/licenses/cache.bin.zstd"); +static CACHE_DATA: &[u8] = include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/resources/licenses/cache.bin.zstd" +)); const MIN_THRESHOLD: f32 = 0.8; pub struct Detector {