diff --git a/.rustfmt.toml b/.rustfmt.toml index 3a79d0ed4..43d4840c7 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,2 +1 @@ -hard_tabs = true newline_style = "Unix" diff --git a/resources/f77.ascii b/resources/f77.ascii new file mode 100644 index 000000000..b7c0b310a --- /dev/null +++ b/resources/f77.ascii @@ -0,0 +1,20 @@ + +{4} _ {1}__ __ +{4} _|_ {1} / / +{0} o{4}|{1} / / + +{0} /\ +{0} / \ +{0} | | +{0} |{2}NASA{0}| +{0} | | +{0} | | +{0} | | +{0} ' ' +{0} | | +{0} | | +{0} |______| +{3} /-`'-`.\ +{3} ; / . \'\. +{3} '/''( .'\.'' +{3}'.'.;.;' ;'.;' diff --git a/src/cli.rs b/src/cli.rs index 5419915f7..2632ed0e3 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -15,42 +15,42 @@ use strum::IntoEnumIterator; const MAX_TERM_WIDTH: usize = 95; pub struct Config { - pub repo_path: String, - pub ascii_input: Option, - pub ascii_language: Option, - pub ascii_colors: Vec, - pub disabled_fields: InfoFieldOff, - pub no_bold: bool, - pub image: Option, - pub image_backend: Option>, - pub image_color_resolution: usize, - pub no_merges: bool, - pub no_color_palette: bool, - pub number_of_authors: usize, - pub ignored_directories: Vec, - pub bot_regex_pattern: Option, - pub print_languages: bool, - pub print_package_managers: bool, - pub output: Option, - pub true_color: bool, - pub art_off: bool, - pub text_colors: Vec, - pub iso_time: bool, - pub show_email: bool, - pub include_hidden: bool, - pub language_types: Vec, + pub repo_path: String, + pub ascii_input: Option, + pub ascii_language: Option, + pub ascii_colors: Vec, + pub disabled_fields: InfoFieldOff, + pub no_bold: bool, + pub image: Option, + pub image_backend: Option>, + pub image_color_resolution: usize, + pub no_merges: bool, + pub no_color_palette: bool, + pub number_of_authors: usize, + pub ignored_directories: Vec, + pub bot_regex_pattern: Option, + pub print_languages: bool, + pub print_package_managers: bool, + pub output: Option, + pub true_color: bool, + pub art_off: bool, + pub text_colors: Vec, + pub iso_time: bool, + pub show_email: bool, + pub include_hidden: bool, + pub language_types: Vec, } impl Config { - pub fn new() -> Result { - #[cfg(not(windows))] - let possible_backends = ["kitty", "iterm", "sixel"]; - #[cfg(windows)] - let possible_backends = []; - let color_values = &[ - "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", - ]; - let matches = App::new(crate_name!()) + pub fn new() -> Result { + #[cfg(not(windows))] + let possible_backends = ["kitty", "iterm", "sixel"]; + #[cfg(windows)] + let possible_backends = []; + let color_values = &[ + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", + ]; + let matches = App::new(crate_name!()) .version(crate_version!()) .about(crate_description!()) .setting(AppSettings::ColoredHelp) @@ -296,173 +296,173 @@ impl Config { ) .get_matches(); - let true_color = match matches.value_of("true-color") { - Some("always") => true, - Some("never") => false, - Some("auto") => is_truecolor_terminal(), - _ => unreachable!(), - }; + let true_color = match matches.value_of("true-color") { + Some("always") => true, + Some("never") => false, + Some("auto") => is_truecolor_terminal(), + _ => unreachable!(), + }; - let no_bold = matches.is_present("no-bold"); - let no_merges = matches.is_present("no-merges"); - let no_color_palette = matches.is_present("no-palette"); - let print_languages = matches.is_present("languages"); - let print_package_managers = matches.is_present("package-managers"); - let iso_time = matches.is_present("isotime"); - let show_email = matches.is_present("email"); - let include_hidden = matches.is_present("hidden"); + let no_bold = matches.is_present("no-bold"); + let no_merges = matches.is_present("no-merges"); + let no_color_palette = matches.is_present("no-palette"); + let print_languages = matches.is_present("languages"); + let print_package_managers = matches.is_present("package-managers"); + let iso_time = matches.is_present("isotime"); + let show_email = matches.is_present("email"); + let include_hidden = matches.is_present("hidden"); - let output = matches - .value_of("output") - .map(SerializationFormat::from_str) - .transpose()?; + let output = matches + .value_of("output") + .map(SerializationFormat::from_str) + .transpose()?; - let fields_to_hide: Vec = if let Some(values) = matches.values_of("disable-fields") - { - values.map(String::from).collect() - } else { - Vec::new() - }; + let fields_to_hide: Vec = if let Some(values) = matches.values_of("disable-fields") + { + values.map(String::from).collect() + } else { + Vec::new() + }; - let disabled_fields = InfoFieldOff::new(fields_to_hide)?; + let disabled_fields = InfoFieldOff::new(fields_to_hide)?; - let art_off = match matches.value_of("show-logo") { - Some("always") => false, - Some("never") => true, - Some("auto") => { - if let Some((width, _)) = term_size::dimensions_stdout() { - width < MAX_TERM_WIDTH - } else { - false - } - } - _ => unreachable!(), - }; + let art_off = match matches.value_of("show-logo") { + Some("always") => false, + Some("never") => true, + Some("auto") => { + if let Some((width, _)) = term_size::dimensions_stdout() { + width < MAX_TERM_WIDTH + } else { + false + } + } + _ => unreachable!(), + }; - let image = if let Some(image_path) = matches.value_of("image") { - Some(image::open(image_path).with_context(|| "Could not load the specified image")?) - } else { - None - }; + let image = if let Some(image_path) = matches.value_of("image") { + Some(image::open(image_path).with_context(|| "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 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 image_color_resolution = if let Some(value) = matches.value_of("color-resolution") { - usize::from_str(value)? - } else { - 16 - }; + let image_color_resolution = if let Some(value) = matches.value_of("color-resolution") { + usize::from_str(value)? + } else { + 16 + }; - let repo_path = matches - .value_of("input") - .map(String::from) - .with_context(|| "Failed to parse input directory")?; + let repo_path = matches + .value_of("input") + .map(String::from) + .with_context(|| "Failed to parse input directory")?; - let ascii_input = matches.value_of("ascii-input").map(String::from); + let ascii_input = matches.value_of("ascii-input").map(String::from); - let ascii_language = matches - .value_of("ascii-language") - .map(|ascii_language| Language::from_str(&ascii_language.to_lowercase()).unwrap()); + let ascii_language = matches + .value_of("ascii-language") + .map(|ascii_language| Language::from_str(&ascii_language.to_lowercase()).unwrap()); - let ascii_colors = if let Some(values) = matches.values_of("ascii-colors") { - values.map(String::from).collect() - } else { - Vec::new() - }; + let ascii_colors = if let Some(values) = matches.values_of("ascii-colors") { + values.map(String::from).collect() + } else { + Vec::new() + }; - let text_colors = if let Some(values) = matches.values_of("text-colors") { - values.map(String::from).collect() - } else { - Vec::new() - }; + let text_colors = if let Some(values) = matches.values_of("text-colors") { + values.map(String::from).collect() + } else { + Vec::new() + }; - let number_of_authors: usize = matches.value_of("authors-number").unwrap().parse()?; + let number_of_authors: usize = matches.value_of("authors-number").unwrap().parse()?; - let ignored_directories = - if let Some(user_ignored_directories) = matches.values_of("exclude") { - user_ignored_directories.map(String::from).collect() - } else { - Vec::new() - }; + let ignored_directories = + if let Some(user_ignored_directories) = matches.values_of("exclude") { + user_ignored_directories.map(String::from).collect() + } else { + Vec::new() + }; - let bot_regex_pattern = matches.is_present("no-bots").then(|| { - matches - .value_of("no-bots") - .map_or(Regex::from_str(r"\[bot\]").unwrap(), |s| { - Regex::from_str(s).unwrap() - }) - }); + let bot_regex_pattern = matches.is_present("no-bots").then(|| { + matches + .value_of("no-bots") + .map_or(Regex::from_str(r"\[bot\]").unwrap(), |s| { + Regex::from_str(s).unwrap() + }) + }); - let language_types: Vec = if let Some(values) = matches.values_of("type") { - values.map(|t| LanguageType::from_str(t).unwrap()).collect() - } else { - vec![LanguageType::Programming, LanguageType::Markup] - }; + let language_types: Vec = if let Some(values) = matches.values_of("type") { + values.map(|t| LanguageType::from_str(t).unwrap()).collect() + } else { + vec![LanguageType::Programming, LanguageType::Markup] + }; - Ok(Config { - repo_path, - ascii_input, - ascii_language, - ascii_colors, - disabled_fields, - no_bold, - image, - image_backend, - image_color_resolution, - no_merges, - no_color_palette, - number_of_authors, - ignored_directories, - bot_regex_pattern, - print_languages, - print_package_managers, - output, - true_color, - art_off, - text_colors, - iso_time, - show_email, - include_hidden, - language_types, - }) - } + Ok(Config { + repo_path, + ascii_input, + ascii_language, + ascii_colors, + disabled_fields, + no_bold, + image, + image_backend, + image_color_resolution, + no_merges, + no_color_palette, + number_of_authors, + ignored_directories, + bot_regex_pattern, + print_languages, + print_package_managers, + output, + true_color, + art_off, + text_colors, + iso_time, + show_email, + include_hidden, + language_types, + }) + } } pub fn print_supported_languages() -> Result<()> { - for l in Language::iter() { - println!("{}", l); - } + for l in Language::iter() { + println!("{}", l); + } - Ok(()) + Ok(()) } pub fn print_supported_package_managers() -> Result<()> { - for p in PackageManager::iter() { - println!("{}", p); - } + for p in PackageManager::iter() { + println!("{}", p); + } - Ok(()) + Ok(()) } pub fn is_truecolor_terminal() -> bool { - env::var("COLORTERM") - .map(|colorterm| colorterm == "truecolor" || colorterm == "24bit") - .unwrap_or(false) + env::var("COLORTERM") + .map(|colorterm| colorterm == "truecolor" || colorterm == "24bit") + .unwrap_or(false) } pub fn get_git_version() -> String { - let version = Command::new("git").arg("--version").output(); + let version = Command::new("git").arg("--version").output(); - match version { - Ok(v) => String::from_utf8_lossy(&v.stdout).replace('\n', ""), - Err(_) => String::new(), - } + match version { + Ok(v) => String::from_utf8_lossy(&v.stdout).replace('\n', ""), + Err(_) => String::new(), + } } diff --git a/src/info/author.rs b/src/info/author.rs index e88ec0ced..50d12f1f7 100644 --- a/src/info/author.rs +++ b/src/info/author.rs @@ -2,55 +2,55 @@ use serde::ser::SerializeStruct; use serde::Serialize; pub struct Author { - name: String, - email: Option, - nbr_of_commits: usize, - contribution: usize, + name: String, + email: Option, + nbr_of_commits: usize, + contribution: usize, } impl Author { - pub fn new( - name: String, - email: Option, - nbr_of_commits: usize, - total_nbr_of_commits: usize, - ) -> Self { - let contribution = - (nbr_of_commits as f32 * 100. / total_nbr_of_commits as f32).round() as usize; - Self { - name, - email, - nbr_of_commits, - contribution, - } - } + pub fn new( + name: String, + email: Option, + nbr_of_commits: usize, + total_nbr_of_commits: usize, + ) -> Self { + let contribution = + (nbr_of_commits as f32 * 100. / total_nbr_of_commits as f32).round() as usize; + Self { + name, + email, + nbr_of_commits, + contribution, + } + } } impl std::fmt::Display for Author { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - if let Some(email) = &self.email { - write!( - f, - "{}% {} <{}> {}", - self.contribution, self.name, email, self.nbr_of_commits - ) - } else { - write!( - f, - "{}% {} {}", - self.contribution, self.name, self.nbr_of_commits - ) - } - } + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if let Some(email) = &self.email { + write!( + f, + "{}% {} <{}> {}", + self.contribution, self.name, email, self.nbr_of_commits + ) + } else { + write!( + f, + "{}% {} {}", + self.contribution, self.name, self.nbr_of_commits + ) + } + } } impl Serialize for Author { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_struct("Author", 1)?; - state.serialize_field("name", &self.name)?; - state.end() - } + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("Author", 1)?; + state.serialize_field("name", &self.name)?; + state.end() + } } diff --git a/src/info/deps/mod.rs b/src/info/deps/mod.rs index aaa5afc17..9adc2dc4b 100644 --- a/src/info/deps/mod.rs +++ b/src/info/deps/mod.rs @@ -7,43 +7,43 @@ pub mod package_manager; type DependencyParser = fn(&str) -> Result; pub struct DependencyDetector { - package_managers: HashMap, + package_managers: HashMap, } impl DependencyDetector { - pub fn new() -> Self { - let package_managers = package_manager::build(); - - DependencyDetector { package_managers } - } - - pub fn get_dependencies(&self, dir: &str) -> Result { - let deps = fs::read_dir(dir)? - .filter_map(std::result::Result::ok) - .map(|entry| entry.path()) - .filter(|entry| { - entry.is_file() - && entry - .file_name() - .map(OsStr::to_string_lossy) - .map(|s| self.package_managers.contains_key(s.as_ref())) - .unwrap_or_default() - }) - .map(|entry| { - let (parser, found_package_manager) = - &self.package_managers[entry.file_name().unwrap().to_str().unwrap()]; - let contents = fs::read_to_string(entry).unwrap_or_default(); - let number_of_deps = parser(&contents).unwrap(); - match number_of_deps { - 0 => Err(""), - _ => Ok(format!("{} ({})", number_of_deps, found_package_manager)), - } - }) - .filter_map(Result::ok) - .collect::>(); - - let output = deps.join(", "); - - Ok(output) - } + pub fn new() -> Self { + let package_managers = package_manager::build(); + + DependencyDetector { package_managers } + } + + pub fn get_dependencies(&self, dir: &str) -> Result { + let deps = fs::read_dir(dir)? + .filter_map(std::result::Result::ok) + .map(|entry| entry.path()) + .filter(|entry| { + entry.is_file() + && entry + .file_name() + .map(OsStr::to_string_lossy) + .map(|s| self.package_managers.contains_key(s.as_ref())) + .unwrap_or_default() + }) + .map(|entry| { + let (parser, found_package_manager) = + &self.package_managers[entry.file_name().unwrap().to_str().unwrap()]; + let contents = fs::read_to_string(entry).unwrap_or_default(); + let number_of_deps = parser(&contents).unwrap(); + match number_of_deps { + 0 => Err(""), + _ => Ok(format!("{} ({})", number_of_deps, found_package_manager)), + } + }) + .filter_map(Result::ok) + .collect::>(); + + let output = deps.join(", "); + + Ok(output) + } } diff --git a/src/info/deps/package_manager.rs b/src/info/deps/package_manager.rs index 410a3843c..a235dcb39 100644 --- a/src/info/deps/package_manager.rs +++ b/src/info/deps/package_manager.rs @@ -42,86 +42,86 @@ macro_rules! define_package_managers { } define_package_managers! { - { Cargo, "cargo", [ ("Cargo.toml", cargo) ] }, - { GoModules, "go modules", [ ("go.mod", go_modules) ] }, - { Npm, "npm", [ ("package.json", npm) ] }, - { Pip, "pip", [ ("requirements.txt", pip_requirement) ("pyproject.toml", pip_pyproject) ("Pipfile", pip_pipfile) ] }, - { Pub, "pub", [ ("pubspec.yaml", pub_packages) ] }, + { Cargo, "cargo", [ ("Cargo.toml", cargo) ] }, + { GoModules, "go modules", [ ("go.mod", go_modules) ] }, + { Npm, "npm", [ ("package.json", npm) ] }, + { Pip, "pip", [ ("requirements.txt", pip_requirement) ("pyproject.toml", pip_pyproject) ("Pipfile", pip_pipfile) ] }, + { Pub, "pub", [ ("pubspec.yaml", pub_packages) ] }, } fn cargo(contents: &str) -> Result { - let parsed = contents.parse::()?; - let count = parsed.get("dependencies"); + let parsed = contents.parse::()?; + let count = parsed.get("dependencies"); - match count { - Some(val) => Ok(val.as_table().unwrap().len()), - None => Ok(0), - } + match count { + Some(val) => Ok(val.as_table().unwrap().len()), + None => Ok(0), + } } fn go_modules(contents: &str) -> Result { - let mut count = 0; - let mut start = false; - for line in contents.lines() { - if line.contains("require") { - start = true; - continue; - } - - if start && line.contains(')') { - break; - } - - if start { - count += 1; - } - } - - Ok(count) + let mut count = 0; + let mut start = false; + for line in contents.lines() { + if line.contains("require") { + start = true; + continue; + } + + if start && line.contains(')') { + break; + } + + if start { + count += 1; + } + } + + Ok(count) } fn npm(contents: &str) -> Result { - let parsed: serde_json::Value = serde_json::from_str(contents)?; - match &parsed["dependencies"].as_object() { - Some(val) => Ok(val.len()), - None => Ok(0), - } + let parsed: serde_json::Value = serde_json::from_str(contents)?; + match &parsed["dependencies"].as_object() { + Some(val) => Ok(val.len()), + None => Ok(0), + } } fn pip_requirement(contents: &str) -> Result { - let count = Regex::new(r"(^|\n)[A-z]+")?.find_iter(contents).count(); + let count = Regex::new(r"(^|\n)[A-z]+")?.find_iter(contents).count(); - Ok(count) + Ok(count) } fn pip_pyproject(contents: &str) -> Result { - let parsed = contents.parse::()?; - let count = parsed - .get("tool") - .and_then(|tool| tool.get("poetry")) - .and_then(|poetry| poetry.get("dependencies")); - match count { - Some(val) => Ok(val.as_table().unwrap().len()), - None => Ok(0), - } + let parsed = contents.parse::()?; + let count = parsed + .get("tool") + .and_then(|tool| tool.get("poetry")) + .and_then(|poetry| poetry.get("dependencies")); + match count { + Some(val) => Ok(val.as_table().unwrap().len()), + None => Ok(0), + } } fn pip_pipfile(contents: &str) -> Result { - let parsed = contents.parse::()?; - let count = parsed.get("packages"); + let parsed = contents.parse::()?; + let count = parsed.get("packages"); - match count { - Some(val) => Ok(val.as_table().unwrap().len()), - None => Ok(0), - } + match count { + Some(val) => Ok(val.as_table().unwrap().len()), + None => Ok(0), + } } fn pub_packages(contents: &str) -> Result { - match YamlLoader::load_from_str(contents) { - Ok(parsed) => match &parsed[0]["dependencies"].as_hash() { - Some(deps) => Ok(deps.len()), - None => Ok(0), - }, - Err(_) => Ok(0), - } + match YamlLoader::load_from_str(contents) { + Ok(parsed) => match &parsed[0]["dependencies"].as_hash() { + Some(deps) => Ok(deps.len()), + None => Ok(0), + }, + Err(_) => Ok(0), + } } diff --git a/src/info/head_refs.rs b/src/info/head_refs.rs index 9228f19b8..fdf7319cc 100644 --- a/src/info/head_refs.rs +++ b/src/info/head_refs.rs @@ -3,44 +3,44 @@ use serde::ser::SerializeStruct; use serde::Serialize; pub struct HeadRefs { - commit: Oid, - refs: Vec, + commit: Oid, + refs: Vec, } impl HeadRefs { - pub fn new(commit: Oid, refs: Vec) -> HeadRefs { - HeadRefs { commit, refs } - } + pub fn new(commit: Oid, refs: Vec) -> HeadRefs { + HeadRefs { commit, refs } + } } impl std::fmt::Display for HeadRefs { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let short_commit = self.commit.to_string().chars().take(7).collect::(); - if !self.refs.is_empty() { - let refs_str = self - .refs - .iter() - .map(|ref_name| ref_name.as_str()) - .collect::>() - .join(", "); - write!(f, "{} ({})", short_commit, refs_str) - } else { - write!(f, "{}", short_commit) - } - } + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let short_commit = self.commit.to_string().chars().take(7).collect::(); + if !self.refs.is_empty() { + let refs_str = self + .refs + .iter() + .map(|ref_name| ref_name.as_str()) + .collect::>() + .join(", "); + write!(f, "{} ({})", short_commit, refs_str) + } else { + write!(f, "{}", short_commit) + } + } } impl Serialize for HeadRefs { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_struct("HeadRefs", 2)?; - state.serialize_field("refs", &self.refs)?; - state.serialize_field( - "oid", - &self.commit.to_string().chars().take(7).collect::(), - )?; - state.end() - } + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("HeadRefs", 2)?; + state.serialize_field("refs", &self.refs)?; + state.serialize_field( + "oid", + &self.commit.to_string().chars().take(7).collect::(), + )?; + state.end() + } } diff --git a/src/info/info_field.rs b/src/info/info_field.rs index 83430351a..31216bf04 100644 --- a/src/info/info_field.rs +++ b/src/info/info_field.rs @@ -5,73 +5,73 @@ use strum::{EnumCount, EnumIter, EnumString, IntoStaticStr}; #[derive(PartialEq, Eq, EnumString, EnumCount, EnumIter, IntoStaticStr)] #[strum(serialize_all = "snake_case")] pub enum InfoField { - GitInfo, - Project, - Head, - Pending, - Version, - Created, - Languages, - Dependencies, - Authors, - LastChange, - Contributors, - Repo, - Commits, - LinesOfCode, - Size, - License, + GitInfo, + Project, + Head, + Pending, + Version, + Created, + Languages, + Dependencies, + Authors, + LastChange, + Contributors, + Repo, + Commits, + LinesOfCode, + Size, + License, } #[derive(Default)] pub struct InfoFieldOff { - pub git_info: bool, - pub project: bool, - pub head: bool, - pub pending: bool, - pub version: bool, - pub created: bool, - pub languages: bool, - pub dependencies: bool, - pub authors: bool, - pub last_change: bool, - pub contributors: bool, - pub repo: bool, - pub commits: bool, - pub lines_of_code: bool, - pub size: bool, - pub license: bool, + pub git_info: bool, + pub project: bool, + pub head: bool, + pub pending: bool, + pub version: bool, + pub created: bool, + pub languages: bool, + pub dependencies: bool, + pub authors: bool, + pub last_change: bool, + pub contributors: bool, + pub repo: bool, + pub commits: bool, + pub lines_of_code: bool, + pub size: bool, + pub license: bool, } impl InfoFieldOff { - pub fn new(fields_to_hide: Vec) -> Result { - let mut info_field_off = InfoFieldOff { - ..Default::default() - }; + pub fn new(fields_to_hide: Vec) -> Result { + let mut info_field_off = InfoFieldOff { + ..Default::default() + }; - for field in fields_to_hide.iter() { - let item = InfoField::from_str(field.to_lowercase().as_str())?; + for field in fields_to_hide.iter() { + let item = InfoField::from_str(field.to_lowercase().as_str())?; - match item { - InfoField::GitInfo => info_field_off.git_info = true, - InfoField::Project => info_field_off.project = true, - InfoField::Head => info_field_off.head = true, - InfoField::Pending => info_field_off.pending = true, - InfoField::Version => info_field_off.version = true, - InfoField::Created => info_field_off.created = true, - InfoField::Languages => info_field_off.languages = true, - InfoField::Dependencies => info_field_off.dependencies = true, - InfoField::Authors => info_field_off.authors = true, - InfoField::LastChange => info_field_off.last_change = true, - InfoField::Contributors => info_field_off.contributors = true, - InfoField::Repo => info_field_off.repo = true, - InfoField::Commits => info_field_off.commits = true, - InfoField::LinesOfCode => info_field_off.lines_of_code = true, - InfoField::Size => info_field_off.size = true, - InfoField::License => info_field_off.license = true, - } - } + match item { + InfoField::GitInfo => info_field_off.git_info = true, + InfoField::Project => info_field_off.project = true, + InfoField::Head => info_field_off.head = true, + InfoField::Pending => info_field_off.pending = true, + InfoField::Version => info_field_off.version = true, + InfoField::Created => info_field_off.created = true, + InfoField::Languages => info_field_off.languages = true, + InfoField::Dependencies => info_field_off.dependencies = true, + InfoField::Authors => info_field_off.authors = true, + InfoField::LastChange => info_field_off.last_change = true, + InfoField::Contributors => info_field_off.contributors = true, + InfoField::Repo => info_field_off.repo = true, + InfoField::Commits => info_field_off.commits = true, + InfoField::LinesOfCode => info_field_off.lines_of_code = true, + InfoField::Size => info_field_off.size = true, + InfoField::License => info_field_off.license = true, + } + } - Ok(info_field_off) - } + Ok(info_field_off) + } } diff --git a/src/info/langs/language.rs b/src/info/langs/language.rs index 6258bb97b..0f4ba779a 100644 --- a/src/info/langs/language.rs +++ b/src/info/langs/language.rs @@ -4,8 +4,8 @@ use std::env; use strum::{EnumIter, EnumString, IntoStaticStr}; pub struct Colors { - basic_colors: Vec, - true_colors: Option>, + basic_colors: Vec, + true_colors: Option>, } macro_rules! define_colors { @@ -17,10 +17,10 @@ macro_rules! define_colors { #[derive(PartialEq, EnumString, EnumIter, IntoStaticStr)] #[strum(serialize_all = "lowercase")] pub enum LanguageType { - Programming, - Markup, - Prose, - Data, + Programming, + Markup, + Prose, + Data, } macro_rules! define_languages { @@ -173,131 +173,132 @@ macro_rules! define_languages { } define_languages! { - { Ada, Programming, "ada.ascii", define_colors!( [Color::White, Color::Cyan, Color::Blue] : [Color::TrueColor{r:255, g:255, b:255}, Color::TrueColor{r:0, g:24, b:201}, Color::TrueColor{r:12, g:10, b:124}] ) }, - { Assembly, Programming, "assembly.ascii", define_colors!( [Color::Cyan] ) }, - { AutoHotKey, Programming, "autohotkey.ascii", define_colors!( [Color::White, Color::Green] : [Color::TrueColor{r:255, g:255, b:255}, Color::TrueColor{r: 0x11, g: 0x98, b: 0x10}]) }, - { Bash, Programming, "bash.ascii", define_colors!( [Color::White] ), "bash" }, - { C, Programming, "c.ascii", define_colors!( [Color::Cyan, Color::Blue, Color::White] : [Color::TrueColor{r:93, g:108, b:191}, Color::TrueColor{r:41, g:54, b:147}, Color::TrueColor{r:255, g:255, b:255}] ) }, - { Clojure, Programming, "clojure.ascii", define_colors!( [Color::Cyan, Color::Green] ) }, - { CMake, Programming, "cmake.ascii", define_colors!( [Color::Blue, Color::Green, Color::Red, Color::Black] ) }, - { CoffeeScript, Programming, "coffeescript.ascii", define_colors!( [Color::Red] ) }, - { Coq, Programming, "coq.ascii", define_colors!( [Color::Yellow, Color::White] : [Color::TrueColor {r:191, g:140, b:94}, Color::TrueColor {r:213, g:190, b:153}] ) }, - { Cpp, Programming, "cpp.ascii", define_colors!( [Color::Cyan, Color::Blue, Color::White] : [Color::TrueColor{r:100, g:154, b:210}, Color::TrueColor{r:0, g:68, b:130}, Color::TrueColor{r:255, g:255, b:255}] ), "c++" }, - { Crystal, Programming, "crystal.ascii", define_colors!( [Color::White, Color::Black] ) }, - { CSharp, Programming, "csharp.ascii", define_colors!( [Color::Blue, Color::Magenta, Color::White] : [Color::TrueColor{r:154, g:73, b:147}, Color::TrueColor{r:106, g:21, b:119}, Color::TrueColor{r:255, g:255, b:255}] ), "c#" }, - { Css, Markup, "css.ascii", define_colors!( [Color::Blue, Color::White] ) }, - { D, Programming, "d.ascii", define_colors!( [Color::Red] ) }, - { Dart, Programming, "dart.ascii", define_colors!( [Color::Blue, Color::Cyan, Color::Blue ] : [Color::TrueColor{ r:0, g:163, b:231 }, Color::TrueColor{ r:66, g:223, b:205 }, Color::TrueColor{ r:1, g:89, b:125 }] ) }, - { Dockerfile, Programming, "dockerfile.ascii", define_colors!( [Color::Cyan, Color::White, Color::Cyan] ) }, - { Elisp, Programming, "emacslisp.ascii", define_colors!( [Color::Magenta, Color::White] ), "emacslisp" }, - { Elixir, Programming, "elixir.ascii", define_colors!( [Color::Magenta] ) }, - { Elm, Programming, "elm.ascii", define_colors!( [Color::Blue, Color::Green, Color::Yellow, Color::Cyan] ) }, - { Emojicode, Programming, "emojicode.ascii", define_colors!( [Color::Green, Color::Magenta, Color::Magenta, Color::Magenta] : [Color::TrueColor{r:119, g:178, b:85}, Color::TrueColor{r:146, g:102, b:204}, Color::TrueColor{r:170, g:141, b:216}, Color::TrueColor{r:116, g:78, b:170}] ) }, - { Erlang, Programming, "erlang.ascii", define_colors!( [Color::Red] ) }, - { Fish, Programming, "fish.ascii", define_colors!( [Color::Red, Color::Yellow] ) }, - { Forth, Programming, "forth.ascii", define_colors!( [Color::Red] ) }, - { FortranModern, Programming, "f90.ascii", define_colors!( [Color::White, Color::Green, Color::Cyan, Color::Yellow, Color::Red] ), "fortran" }, - { FSharp, Programming, "fsharp.ascii", define_colors!( [Color::Cyan, Color::Cyan] ), "f#" }, - { GdScript, Programming, "gdscript.ascii", define_colors!( [Color::Cyan, Color::White] : [Color::TrueColor{ r:69, g:141, b:192 }, Color::TrueColor{ r:255, g:255, b:255}] ) }, - { Go, Programming, "go.ascii", define_colors!( [Color::Cyan, Color::White, Color::Yellow] : [Color::TrueColor{ r:116, g:205, b:221 }, Color::TrueColor{ r:255, g:255, b:255 }, Color::TrueColor{ r:246, g:210, b:162 }] ) }, - { Graphql, Data, "graphql.ascii", define_colors!( [Color::Magenta] ) }, - { Groovy, Programming, "groovy.ascii", define_colors!( [Color::Cyan, Color::White] ) }, - { Haskell, Programming, "haskell.ascii", define_colors!( [Color::Cyan, Color::Magenta, Color::Blue] : [Color::TrueColor{ r:69, g:58, b:98 }, Color::TrueColor{ r:94, g:80, b:134 }, Color::TrueColor{ r:143, g:78, b:139 }] ) }, - { Haxe, Programming, "haxe.ascii", define_colors!( [Color::Yellow, Color::Yellow, Color::Yellow] : [Color::TrueColor{ r: 250, g: 178, b: 11 }, Color::TrueColor{ r:246, g:153, b:18 }, Color::TrueColor{ r: 244, g: 114, b: 22 }] ) }, - { Hcl, Programming, "hcl.ascii", define_colors!( [Color::Magenta, Color::Magenta] : [Color::TrueColor{ r: 95, g: 67, b: 233 }, Color::TrueColor{ r: 64, g: 64, b: 178 }] ) }, - { HolyC, Programming, "holyc.ascii", define_colors!( [Color::Yellow, Color::Cyan, Color::White] : [Color::TrueColor{ r:251, g:254 ,b:103}, Color::TrueColor{ r:11, g:68 ,b:157}, Color::TrueColor{ r:255, g:255 ,b:255} ]) }, - { Html, Markup, "html.ascii", define_colors!( [Color::Red, Color::White] ) }, - { Idris, Programming, "idris.ascii", define_colors!( [Color::Red] ) }, - { Java, Programming, "java.ascii", define_colors!( [Color::Red, Color::Blue] : [Color::TrueColor{ r:244, g:67 ,b:54}, Color::TrueColor{ r:22, g:101 ,b:192} ] ) }, - { JavaScript, Programming, "javascript.ascii", define_colors!( [Color::Yellow] : [Color::TrueColor{ r:236, g:230 ,b:83} ]) }, - { Json, Data, "json.ascii", define_colors!( [Color::White, Color::Black] ) }, - { Jsonnet, Programming, "jsonnet.ascii", define_colors!( [Color::White, Color::Black] ) }, - { Jsx, Programming, "jsx.ascii", define_colors!( [Color::Yellow] ) }, - { Julia, Programming, "julia.ascii", define_colors!( [Color::White, Color::Blue, Color::Green, Color::Red, Color::Magenta] ) }, - { Jupyter, Programming, "jupyter.ascii", define_colors!( [Color::White, Color::Yellow, Color::White] : [Color::TrueColor{ r:255, g:255 ,b:255}, Color::TrueColor{ r:255, g:112 ,b:15}, Color::TrueColor{ r:158, g:158 ,b:158} ] ), "jupyter-notebooks" }, - { Kotlin, Programming, "kotlin.ascii", define_colors!( [Color::Blue, Color::Yellow, Color::Magenta] ) }, - { Lisp, Programming, "lisp.ascii", define_colors!( [Color::White] ) }, - { Lua, Programming, "lua.ascii", define_colors!( [Color::Blue, Color::White, Color::White] : [Color::TrueColor{ r:46, g:0 ,b:127}, Color::TrueColor{ r:128, g:128 ,b:128}, Color::TrueColor{ r:255, g:255 ,b:255} ] ) }, - { LLVM, Programming, "llvm.ascii", define_colors!( [Color::Red] : [Color::TrueColor{ r:152, g:1 ,b:46}] ) }, - { Markdown, Prose, "markdown.ascii", define_colors!( [Color::White, Color::Red] ) }, - { Nim, Programming, "nim.ascii", define_colors!( [Color::Yellow, Color::White] ) }, - { Nix, Programming, "nix.ascii", define_colors!( [Color::Cyan, Color::Blue] ) }, - { ObjectiveC, Programming, "objectivec.ascii", define_colors!( [Color::Cyan, Color::Blue] ), "objective-c" }, - { OCaml, Programming, "ocaml.ascii", define_colors!( [Color::Yellow] ) }, - { Org, Prose, "org.ascii", define_colors!( [Color::Green, Color::Red, Color::White] ) }, - { Perl, Programming, "perl.ascii", define_colors!( [Color::Cyan] ) }, - { Php, Programming, "php.ascii", define_colors!( [Color::Magenta, Color::Blue, Color::Cyan, Color::White] ) }, - { PowerShell, Programming, "powershell.ascii", define_colors!( [Color::Blue, Color::White] : [Color::TrueColor{ r:49, g:108, b:185}, Color::TrueColor{ r:255, g:255, b:255} ] ) }, - { Processing, Programming, "processing.ascii", define_colors!( [Color::Blue, Color::White] : [Color::TrueColor{ r:80, g:80 ,b:80}, Color::TrueColor{ r:255, g:255 ,b:255} ] ) }, - { Prolog, Programming, "prolog.ascii", define_colors!( [Color::White] ) }, - { Protobuf, Programming, "protobuf.ascii", define_colors!( [Color::Red, Color::Blue, Color::Green, Color::Yellow] )}, - { PureScript, Programming, "purescript.ascii", define_colors!( [Color::White] ) }, - { Python, Programming, "python.ascii", define_colors!( [Color::Blue, Color::Yellow] : [Color::TrueColor{ r:47, g:105 ,b:162}, Color::TrueColor{ r:255, g:217 ,b:64} ] ) }, - { Qml, Programming, "qml.ascii", define_colors!( [Color::Green, Color::White, Color::Green] : [Color::TrueColor{ r:128, g:195 ,b:66}, Color::TrueColor{ r:255, g:255 ,b:255}, Color::TrueColor{ r:77, g:117 ,b:40} ] ) }, - { R, Programming, "r.ascii", define_colors!( [Color::White, Color::Blue] ) }, - { Racket, Programming, "racket.ascii", define_colors!( [Color::Red, Color::White, Color::Blue] ) }, - { - Perl6, Programming, "raku.ascii", define_colors!( [ - Color::Blue, - Color::Red, - Color::Yellow, - Color::White, - Color::Green - ] : [ - Color::TrueColor{ r:91, g:0, b:253 }, - Color::TrueColor{ r:255, g:0, b:94 }, - Color::TrueColor{ r:243, g:255, b:39 }, - Color::TrueColor{ r:255, g:255, b:255 }, - Color::TrueColor{ r:0, g:255, b:57 } - ] ), - "raku" - }, - { Ruby, Programming, "ruby.ascii", define_colors!( [Color::Red] : [Color::TrueColor{ r: 204, g: 52, b: 45 }] ) }, - { Rust, Programming, "rust.ascii", define_colors!( [Color::Red, Color::White] : [Color::TrueColor{ r:228, g:55 ,b:23}, Color::TrueColor{ r:255, g:255 ,b:255} ] ) }, - { Sass, Markup, "sass.ascii", define_colors!( [Color::Magenta] : [Color::TrueColor{ r:205, g:103 ,b:153} ] ) }, - { Scala, Programming, "scala.ascii", define_colors!( [Color::Red, Color::Red] : [Color::TrueColor{ r:223, g:63 ,b:61}, Color::TrueColor{ r:127, g:12 ,b:29} ] ) }, - { Scheme, Programming, "scheme.ascii", define_colors!( [Color::White] : [Color::TrueColor{r: 85, g:85, b:85}] ) }, - { Sh, Programming, "shell.ascii", define_colors!( [Color::Green] ), "shell" }, - { Solidity, Programming, "solidity.ascii", define_colors!( [ Color::White, Color::Black, Color::Black, Color::Black, Color::Black] : [ Color::White, Color::TrueColor{ r: 46, g: 46, b: 46 }, Color::TrueColor{ r: 26, g: 26, b: 26 }, Color::TrueColor{ r: 51, g: 51, b: 51 }, Color::TrueColor{ r: 81, g: 81, b: 81 } ] ) }, - { Sql, Data, "sql.ascii", define_colors!( [Color::Cyan, Color::Yellow] ) }, - { Svelte, Markup, "svelte.ascii", define_colors!( [Color::Red, Color::White] : [Color::TrueColor{ r: 255, g: 60, b: 0 }, Color::TrueColor{ r: 255, g: 255, b: 255 }] ) }, - { - Swift, Programming, "swift.ascii", define_colors!( [ - Color::Red, - Color::Red, - Color::Red, - Color::Red, - Color::Red, - Color::Red, - Color::Red, - Color::Red, - Color::Red, - Color::Red - ] : [ - Color::TrueColor{ r:248, g:129, b:52 }, - Color::TrueColor{ r:249, g:119, b:50 }, - Color::TrueColor{ r:249, g:109, b:48 }, - Color::TrueColor{ r:250, g:99, b:46 }, - Color::TrueColor{ r:250, g:89, b:44 }, - Color::TrueColor{ r:251, g:80, b:42 }, - Color::TrueColor{ r:251, g:70, b:40 }, - Color::TrueColor{ r:252, g:60, b:38 }, - Color::TrueColor{ r:252, g:50, b:36 }, - Color::TrueColor{ r:253, g:40, b:34 } - ] ) - }, - { Tcl, Programming, "tcl.ascii", define_colors!( [Color::Blue, Color::White, Color::Cyan] ) }, - { Tex, Markup, "tex.ascii", define_colors!( [Color::White, Color::Black] ) }, - { Toml, Data, "toml.ascii", define_colors!( [Color::Red, Color::White] : [Color::TrueColor{ r:156, g:66, b:33}, Color::TrueColor{ r:255, g:255, b:255} ]) }, - { Tsx, Programming, "tsx.ascii", define_colors!( [Color::Blue] ) }, - { TypeScript, Programming, "typescript.ascii", define_colors!( [Color::Cyan, Color::White] : [Color::TrueColor{ r:0, g:122, b:204}, Color::TrueColor{ r:255, g:255, b:255} ]) }, - { Vala, Programming, "vala.ascii", define_colors!( [Color::Magenta, Color::White] ) }, - { VimScript, Programming, "vimscript.ascii", define_colors!( [Color::Green, Color::Black, Color::White] ) }, - { Vue, Programming, "vue.ascii", define_colors!( [Color::Green, Color::Blue] ) }, - { WebAssembly, Programming, "webassembly.ascii", define_colors!( [Color::Magenta, Color::White] : [Color::TrueColor{ r:101, g:79, b:240}, Color::TrueColor{ r:255, g:255, b:255} ]) }, - { Xaml, Data, "xaml.ascii", define_colors!( [Color::Blue, Color::White] : [Color::TrueColor{ r:51, g:120, b:206}, Color::TrueColor{ r:255, g:255, b:255} ]) }, - { Xml, Data, "xml.ascii", define_colors!( [Color::Yellow, Color::White, Color::Green] ) }, - { Yaml, Data, "yaml.ascii", define_colors!( [Color::White] ) }, - { Zig, Programming, "zig.ascii", define_colors!( [Color::Yellow] ) }, - { Zsh, Programming, "zsh.ascii", define_colors!( [Color::White] ) }, + { Ada, Programming, "ada.ascii", define_colors!( [Color::White, Color::Cyan, Color::Blue] : [Color::TrueColor{r:255, g:255, b:255}, Color::TrueColor{r:0, g:24, b:201}, Color::TrueColor{r:12, g:10, b:124}] ) }, + { Assembly, Programming, "assembly.ascii", define_colors!( [Color::Cyan] ) }, + { AutoHotKey, Programming, "autohotkey.ascii", define_colors!( [Color::White, Color::Green] : [Color::TrueColor{r:255, g:255, b:255}, Color::TrueColor{r: 0x11, g: 0x98, b: 0x10}]) }, + { Bash, Programming, "bash.ascii", define_colors!( [Color::White] ), "bash" }, + { C, Programming, "c.ascii", define_colors!( [Color::Cyan, Color::Blue, Color::White] : [Color::TrueColor{r:93, g:108, b:191}, Color::TrueColor{r:41, g:54, b:147}, Color::TrueColor{r:255, g:255, b:255}] ) }, + { Clojure, Programming, "clojure.ascii", define_colors!( [Color::Cyan, Color::Green] ) }, + { CMake, Programming, "cmake.ascii", define_colors!( [Color::Blue, Color::Green, Color::Red, Color::Black] ) }, + { CoffeeScript, Programming, "coffeescript.ascii", define_colors!( [Color::Red] ) }, + { Coq, Programming, "coq.ascii", define_colors!( [Color::Yellow, Color::White] : [Color::TrueColor {r:191, g:140, b:94}, Color::TrueColor {r:213, g:190, b:153}] ) }, + { Cpp, Programming, "cpp.ascii", define_colors!( [Color::Cyan, Color::Blue, Color::White] : [Color::TrueColor{r:100, g:154, b:210}, Color::TrueColor{r:0, g:68, b:130}, Color::TrueColor{r:255, g:255, b:255}] ), "c++" }, + { Crystal, Programming, "crystal.ascii", define_colors!( [Color::White, Color::Black] ) }, + { CSharp, Programming, "csharp.ascii", define_colors!( [Color::Blue, Color::Magenta, Color::White] : [Color::TrueColor{r:154, g:73, b:147}, Color::TrueColor{r:106, g:21, b:119}, Color::TrueColor{r:255, g:255, b:255}] ), "c#" }, + { Css, Markup, "css.ascii", define_colors!( [Color::Blue, Color::White] ) }, + { D, Programming, "d.ascii", define_colors!( [Color::Red] ) }, + { Dart, Programming, "dart.ascii", define_colors!( [Color::Blue, Color::Cyan, Color::Blue ] : [Color::TrueColor{ r:0, g:163, b:231 }, Color::TrueColor{ r:66, g:223, b:205 }, Color::TrueColor{ r:1, g:89, b:125 }] ) }, + { Dockerfile, Programming, "dockerfile.ascii", define_colors!( [Color::Cyan, Color::White, Color::Cyan] ) }, + { Elisp, Programming, "emacslisp.ascii", define_colors!( [Color::Magenta, Color::White] ), "emacslisp" }, + { Elixir, Programming, "elixir.ascii", define_colors!( [Color::Magenta] ) }, + { Elm, Programming, "elm.ascii", define_colors!( [Color::Blue, Color::Green, Color::Yellow, Color::Cyan] ) }, + { Emojicode, Programming, "emojicode.ascii", define_colors!( [Color::Green, Color::Magenta, Color::Magenta, Color::Magenta] : [Color::TrueColor{r:119, g:178, b:85}, Color::TrueColor{r:146, g:102, b:204}, Color::TrueColor{r:170, g:141, b:216}, Color::TrueColor{r:116, g:78, b:170}] ) }, + { Erlang, Programming, "erlang.ascii", define_colors!( [Color::Red] ) }, + { Fish, Programming, "fish.ascii", define_colors!( [Color::Red, Color::Yellow] ) }, + { Forth, Programming, "forth.ascii", define_colors!( [Color::Red] ) }, + { FortranModern, Programming, "f90.ascii", define_colors!( [Color::White, Color::Green, Color::Cyan, Color::Yellow, Color::Red] ), "fortran" }, + { FortranLegacy, Programming, "f77.ascii", define_colors!( [Color::White, Color::Green, Color::Cyan, Color::Yellow, Color::Red] ), "fortran-legacy" }, + { FSharp, Programming, "fsharp.ascii", define_colors!( [Color::Cyan, Color::Cyan] ), "f#" }, + { GdScript, Programming, "gdscript.ascii", define_colors!( [Color::Cyan, Color::White] : [Color::TrueColor{ r:69, g:141, b:192 }, Color::TrueColor{ r:255, g:255, b:255}] ) }, + { Go, Programming, "go.ascii", define_colors!( [Color::Cyan, Color::White, Color::Yellow] : [Color::TrueColor{ r:116, g:205, b:221 }, Color::TrueColor{ r:255, g:255, b:255 }, Color::TrueColor{ r:246, g:210, b:162 }] ) }, + { Graphql, Data, "graphql.ascii", define_colors!( [Color::Magenta] ) }, + { Groovy, Programming, "groovy.ascii", define_colors!( [Color::Cyan, Color::White] ) }, + { Haskell, Programming, "haskell.ascii", define_colors!( [Color::Cyan, Color::Magenta, Color::Blue] : [Color::TrueColor{ r:69, g:58, b:98 }, Color::TrueColor{ r:94, g:80, b:134 }, Color::TrueColor{ r:143, g:78, b:139 }] ) }, + { Haxe, Programming, "haxe.ascii", define_colors!( [Color::Yellow, Color::Yellow, Color::Yellow] : [Color::TrueColor{ r: 250, g: 178, b: 11 }, Color::TrueColor{ r:246, g:153, b:18 }, Color::TrueColor{ r: 244, g: 114, b: 22 }] ) }, + { Hcl, Programming, "hcl.ascii", define_colors!( [Color::Magenta, Color::Magenta] : [Color::TrueColor{ r: 95, g: 67, b: 233 }, Color::TrueColor{ r: 64, g: 64, b: 178 }] ) }, + { HolyC, Programming, "holyc.ascii", define_colors!( [Color::Yellow, Color::Cyan, Color::White] : [Color::TrueColor{ r:251, g:254 ,b:103}, Color::TrueColor{ r:11, g:68 ,b:157}, Color::TrueColor{ r:255, g:255 ,b:255} ]) }, + { Html, Markup, "html.ascii", define_colors!( [Color::Red, Color::White] ) }, + { Idris, Programming, "idris.ascii", define_colors!( [Color::Red] ) }, + { Java, Programming, "java.ascii", define_colors!( [Color::Red, Color::Blue] : [Color::TrueColor{ r:244, g:67 ,b:54}, Color::TrueColor{ r:22, g:101 ,b:192} ] ) }, + { JavaScript, Programming, "javascript.ascii", define_colors!( [Color::Yellow] : [Color::TrueColor{ r:236, g:230 ,b:83} ]) }, + { Json, Data, "json.ascii", define_colors!( [Color::White, Color::Black] ) }, + { Jsonnet, Programming, "jsonnet.ascii", define_colors!( [Color::White, Color::Black] ) }, + { Jsx, Programming, "jsx.ascii", define_colors!( [Color::Yellow] ) }, + { Julia, Programming, "julia.ascii", define_colors!( [Color::White, Color::Blue, Color::Green, Color::Red, Color::Magenta] ) }, + { Jupyter, Programming, "jupyter.ascii", define_colors!( [Color::White, Color::Yellow, Color::White] : [Color::TrueColor{ r:255, g:255 ,b:255}, Color::TrueColor{ r:255, g:112 ,b:15}, Color::TrueColor{ r:158, g:158 ,b:158} ] ), "jupyter-notebooks" }, + { Kotlin, Programming, "kotlin.ascii", define_colors!( [Color::Blue, Color::Yellow, Color::Magenta] ) }, + { Lisp, Programming, "lisp.ascii", define_colors!( [Color::White] ) }, + { Lua, Programming, "lua.ascii", define_colors!( [Color::Blue, Color::White, Color::White] : [Color::TrueColor{ r:46, g:0 ,b:127}, Color::TrueColor{ r:128, g:128 ,b:128}, Color::TrueColor{ r:255, g:255 ,b:255} ] ) }, + { LLVM, Programming, "llvm.ascii", define_colors!( [Color::Red] : [Color::TrueColor{ r:152, g:1 ,b:46}] ) }, + { Markdown, Prose, "markdown.ascii", define_colors!( [Color::White, Color::Red] ) }, + { Nim, Programming, "nim.ascii", define_colors!( [Color::Yellow, Color::White] ) }, + { Nix, Programming, "nix.ascii", define_colors!( [Color::Cyan, Color::Blue] ) }, + { ObjectiveC, Programming, "objectivec.ascii", define_colors!( [Color::Cyan, Color::Blue] ), "objective-c" }, + { OCaml, Programming, "ocaml.ascii", define_colors!( [Color::Yellow] ) }, + { Org, Prose, "org.ascii", define_colors!( [Color::Green, Color::Red, Color::White] ) }, + { Perl, Programming, "perl.ascii", define_colors!( [Color::Cyan] ) }, + { Php, Programming, "php.ascii", define_colors!( [Color::Magenta, Color::Blue, Color::Cyan, Color::White] ) }, + { PowerShell, Programming, "powershell.ascii", define_colors!( [Color::Blue, Color::White] : [Color::TrueColor{ r:49, g:108, b:185}, Color::TrueColor{ r:255, g:255, b:255} ] ) }, + { Processing, Programming, "processing.ascii", define_colors!( [Color::Blue, Color::White] : [Color::TrueColor{ r:80, g:80 ,b:80}, Color::TrueColor{ r:255, g:255 ,b:255} ] ) }, + { Prolog, Programming, "prolog.ascii", define_colors!( [Color::White] ) }, + { Protobuf, Programming, "protobuf.ascii", define_colors!( [Color::Red, Color::Blue, Color::Green, Color::Yellow] )}, + { PureScript, Programming, "purescript.ascii", define_colors!( [Color::White] ) }, + { Python, Programming, "python.ascii", define_colors!( [Color::Blue, Color::Yellow] : [Color::TrueColor{ r:47, g:105 ,b:162}, Color::TrueColor{ r:255, g:217 ,b:64} ] ) }, + { Qml, Programming, "qml.ascii", define_colors!( [Color::Green, Color::White, Color::Green] : [Color::TrueColor{ r:128, g:195 ,b:66}, Color::TrueColor{ r:255, g:255 ,b:255}, Color::TrueColor{ r:77, g:117 ,b:40} ] ) }, + { R, Programming, "r.ascii", define_colors!( [Color::White, Color::Blue] ) }, + { Racket, Programming, "racket.ascii", define_colors!( [Color::Red, Color::White, Color::Blue] ) }, + { + Perl6, Programming, "raku.ascii", define_colors!( [ + Color::Blue, + Color::Red, + Color::Yellow, + Color::White, + Color::Green + ] : [ + Color::TrueColor{ r:91, g:0, b:253 }, + Color::TrueColor{ r:255, g:0, b:94 }, + Color::TrueColor{ r:243, g:255, b:39 }, + Color::TrueColor{ r:255, g:255, b:255 }, + Color::TrueColor{ r:0, g:255, b:57 } + ] ), + "raku" + }, + { Ruby, Programming, "ruby.ascii", define_colors!( [Color::Red] : [Color::TrueColor{ r: 204, g: 52, b: 45 }] ) }, + { Rust, Programming, "rust.ascii", define_colors!( [Color::Red, Color::White] : [Color::TrueColor{ r:228, g:55 ,b:23}, Color::TrueColor{ r:255, g:255 ,b:255} ] ) }, + { Sass, Markup, "sass.ascii", define_colors!( [Color::Magenta] : [Color::TrueColor{ r:205, g:103 ,b:153} ] ) }, + { Scala, Programming, "scala.ascii", define_colors!( [Color::Red, Color::Red] : [Color::TrueColor{ r:223, g:63 ,b:61}, Color::TrueColor{ r:127, g:12 ,b:29} ] ) }, + { Scheme, Programming, "scheme.ascii", define_colors!( [Color::White] : [Color::TrueColor{r: 85, g:85, b:85}] ) }, + { Sh, Programming, "shell.ascii", define_colors!( [Color::Green] ), "shell" }, + { Solidity, Programming, "solidity.ascii", define_colors!( [ Color::White, Color::Black, Color::Black, Color::Black, Color::Black] : [ Color::White, Color::TrueColor{ r: 46, g: 46, b: 46 }, Color::TrueColor{ r: 26, g: 26, b: 26 }, Color::TrueColor{ r: 51, g: 51, b: 51 }, Color::TrueColor{ r: 81, g: 81, b: 81 } ] ) }, + { Sql, Data, "sql.ascii", define_colors!( [Color::Cyan, Color::Yellow] ) }, + { Svelte, Markup, "svelte.ascii", define_colors!( [Color::Red, Color::White] : [Color::TrueColor{ r: 255, g: 60, b: 0 }, Color::TrueColor{ r: 255, g: 255, b: 255 }] ) }, + { + Swift, Programming, "swift.ascii", define_colors!( [ + Color::Red, + Color::Red, + Color::Red, + Color::Red, + Color::Red, + Color::Red, + Color::Red, + Color::Red, + Color::Red, + Color::Red + ] : [ + Color::TrueColor{ r:248, g:129, b:52 }, + Color::TrueColor{ r:249, g:119, b:50 }, + Color::TrueColor{ r:249, g:109, b:48 }, + Color::TrueColor{ r:250, g:99, b:46 }, + Color::TrueColor{ r:250, g:89, b:44 }, + Color::TrueColor{ r:251, g:80, b:42 }, + Color::TrueColor{ r:251, g:70, b:40 }, + Color::TrueColor{ r:252, g:60, b:38 }, + Color::TrueColor{ r:252, g:50, b:36 }, + Color::TrueColor{ r:253, g:40, b:34 } + ] ) + }, + { Tcl, Programming, "tcl.ascii", define_colors!( [Color::Blue, Color::White, Color::Cyan] ) }, + { Tex, Markup, "tex.ascii", define_colors!( [Color::White, Color::Black] ) }, + { Toml, Data, "toml.ascii", define_colors!( [Color::Red, Color::White] : [Color::TrueColor{ r:156, g:66, b:33}, Color::TrueColor{ r:255, g:255, b:255} ]) }, + { Tsx, Programming, "tsx.ascii", define_colors!( [Color::Blue] ) }, + { TypeScript, Programming, "typescript.ascii", define_colors!( [Color::Cyan, Color::White] : [Color::TrueColor{ r:0, g:122, b:204}, Color::TrueColor{ r:255, g:255, b:255} ]) }, + { Vala, Programming, "vala.ascii", define_colors!( [Color::Magenta, Color::White] ) }, + { VimScript, Programming, "vimscript.ascii", define_colors!( [Color::Green, Color::Black, Color::White] ) }, + { Vue, Programming, "vue.ascii", define_colors!( [Color::Green, Color::Blue] ) }, + { WebAssembly, Programming, "webassembly.ascii", define_colors!( [Color::Magenta, Color::White] : [Color::TrueColor{ r:101, g:79, b:240}, Color::TrueColor{ r:255, g:255, b:255} ]) }, + { Xaml, Data, "xaml.ascii", define_colors!( [Color::Blue, Color::White] : [Color::TrueColor{ r:51, g:120, b:206}, Color::TrueColor{ r:255, g:255, b:255} ]) }, + { Xml, Data, "xml.ascii", define_colors!( [Color::Yellow, Color::White, Color::Green] ) }, + { Yaml, Data, "yaml.ascii", define_colors!( [Color::White] ) }, + { Zig, Programming, "zig.ascii", define_colors!( [Color::Yellow] ) }, + { Zsh, Programming, "zsh.ascii", define_colors!( [Color::White] ) }, } diff --git a/src/info/langs/mod.rs b/src/info/langs/mod.rs index 859fc5b2e..cf8c54b22 100644 --- a/src/info/langs/mod.rs +++ b/src/info/langs/mod.rs @@ -7,112 +7,112 @@ use strum::IntoEnumIterator; pub mod language; pub fn get_dominant_language(languages_stat_vec: &[(Language, f64)]) -> Language { - languages_stat_vec[0].0.clone() + languages_stat_vec[0].0.clone() } pub fn get_language_statistics( - dir: &str, - ignored_directories: &[String], - language_types: &[LanguageType], - include_hidden: bool, + dir: &str, + ignored_directories: &[String], + language_types: &[LanguageType], + include_hidden: bool, ) -> Result<(Vec<(Language, f64)>, usize)> { - let stats = get_statistics(dir, ignored_directories, language_types, include_hidden); - let language_distribution = get_language_distribution(&stats) - .with_context(|| "Could not find any source code in this directory")?; - let mut language_distribution_vec: Vec<(_, _)> = language_distribution.into_iter().collect(); - language_distribution_vec.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap().reverse()); - let loc = get_total_loc(&stats); - Ok((language_distribution_vec, loc)) + let stats = get_statistics(dir, ignored_directories, language_types, include_hidden); + let language_distribution = get_language_distribution(&stats) + .with_context(|| "Could not find any source code in this directory")?; + let mut language_distribution_vec: Vec<(_, _)> = language_distribution.into_iter().collect(); + language_distribution_vec.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap().reverse()); + let loc = get_total_loc(&stats); + Ok((language_distribution_vec, loc)) } fn get_language_distribution(languages: &tokei::Languages) -> Option> { - let mut language_distribution = HashMap::new(); + let mut language_distribution = HashMap::new(); - for (language_name, language) in languages.iter() { - let mut code = language.code; + for (language_name, language) in languages.iter() { + let mut code = language.code; - let has_children = !language.children.is_empty(); + let has_children = !language.children.is_empty(); - if has_children { - for reports in language.children.values() { - for stats in reports.iter().map(|r| r.stats.summarise()) { - code += stats.code; - } - } - } + if has_children { + for reports in language.children.values() { + for stats in reports.iter().map(|r| r.stats.summarise()) { + code += stats.code; + } + } + } - if code == 0 { - continue; - } + if code == 0 { + continue; + } - language_distribution.insert(Language::from(*language_name), code as f64); - } + language_distribution.insert(Language::from(*language_name), code as f64); + } - let total: f64 = language_distribution.iter().map(|(_, v)| v).sum(); + let total: f64 = language_distribution.iter().map(|(_, v)| v).sum(); - if total.abs() < f64::EPSILON { - None - } else { - for (_, val) in language_distribution.iter_mut() { - *val /= total; - *val *= 100_f64; - } + if total.abs() < f64::EPSILON { + None + } else { + for (_, val) in language_distribution.iter_mut() { + *val /= total; + *val *= 100_f64; + } - Some(language_distribution) - } + Some(language_distribution) + } } fn get_total_loc(languages: &tokei::Languages) -> usize { - languages - .values() - .collect::>() - .iter() - .fold(0, |sum, val| sum + val.code) + languages + .values() + .collect::>() + .iter() + .fold(0, |sum, val| sum + val.code) } fn get_statistics( - dir: &str, - ignored_directories: &[String], - language_types: &[LanguageType], - include_hidden: bool, + dir: &str, + ignored_directories: &[String], + language_types: &[LanguageType], + include_hidden: bool, ) -> tokei::Languages { - let mut languages = tokei::Languages::new(); - let supported_languages = get_supported_languages(language_types); - - let tokei_config = tokei::Config { - types: Some(supported_languages), - hidden: Some(include_hidden), - ..tokei::Config::default() - }; - let user_ignored = get_ignored_directories(ignored_directories); - let ignored: Vec<&str> = user_ignored.iter().map(AsRef::as_ref).collect(); - languages.get_statistics(&[&dir], &ignored, &tokei_config); - languages + let mut languages = tokei::Languages::new(); + let supported_languages = get_supported_languages(language_types); + + let tokei_config = tokei::Config { + types: Some(supported_languages), + hidden: Some(include_hidden), + ..tokei::Config::default() + }; + let user_ignored = get_ignored_directories(ignored_directories); + let ignored: Vec<&str> = user_ignored.iter().map(AsRef::as_ref).collect(); + languages.get_statistics(&[&dir], &ignored, &tokei_config); + languages } fn get_supported_languages(types: &[LanguageType]) -> Vec { - Language::iter() - .filter(|language| types.contains(&language.get_type())) - .map(|language| language.into()) - .collect() + Language::iter() + .filter(|language| types.contains(&language.get_type())) + .map(|language| language.into()) + .collect() } fn get_ignored_directories(user_ignored_directories: &[String]) -> Vec { - let mut ignored_directories = Vec::new(); - if !user_ignored_directories.is_empty() { - let re = Regex::new(r"((.*)+/)+(.*)").unwrap(); - for user_ignored_directory in user_ignored_directories { - if re.is_match(user_ignored_directory) { - let prefix = if user_ignored_directory.starts_with('/') { - "**" - } else { - "**/" - }; - ignored_directories.push(format!("{}{}", prefix, user_ignored_directory)); - } else { - ignored_directories.push(String::from(user_ignored_directory)); - } - } - } - ignored_directories + let mut ignored_directories = Vec::new(); + if !user_ignored_directories.is_empty() { + let re = Regex::new(r"((.*)+/)+(.*)").unwrap(); + for user_ignored_directory in user_ignored_directories { + if re.is_match(user_ignored_directory) { + let prefix = if user_ignored_directory.starts_with('/') { + "**" + } else { + "**/" + }; + ignored_directories.push(format!("{}{}", prefix, user_ignored_directory)); + } else { + ignored_directories.push(String::from(user_ignored_directory)); + } + } + } + ignored_directories } diff --git a/src/info/license.rs b/src/info/license.rs index b3af5b7fa..ba93aa34e 100644 --- a/src/info/license.rs +++ b/src/info/license.rs @@ -5,61 +5,61 @@ use std::{ffi::OsStr, fs}; const LICENSE_FILES: [&str; 3] = ["LICENSE", "LICENCE", "COPYING"]; static CACHE_DATA: &[u8] = include_bytes!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/resources/licenses/cache.bin.zstd" + env!("CARGO_MANIFEST_DIR"), + "/resources/licenses/cache.bin.zstd" )); const MIN_THRESHOLD: f32 = 0.8; pub struct Detector { - store: Store, + store: Store, } impl Detector { - pub fn new() -> Result { - match Store::from_cache(CACHE_DATA) { - Ok(store) => Ok(Self { store }), - Err(_) => { - bail!("Could not initialize the license detector") - } - } - } + pub fn new() -> Result { + match Store::from_cache(CACHE_DATA) { + Ok(store) => Ok(Self { store }), + Err(_) => { + bail!("Could not initialize the license detector") + } + } + } - pub fn get_license(&self, dir: &str) -> Result { - fn is_license_file>(file_name: S) -> bool { - LICENSE_FILES - .iter() - .any(|&name| file_name.as_ref().starts_with(name)) - } + pub fn get_license(&self, dir: &str) -> Result { + fn is_license_file>(file_name: S) -> bool { + LICENSE_FILES + .iter() + .any(|&name| file_name.as_ref().starts_with(name)) + } - let mut output = fs::read_dir(dir)? - .filter_map(std::result::Result::ok) - .map(|entry| entry.path()) - .filter(|entry| { - entry.is_file() - && entry - .file_name() - .map(OsStr::to_string_lossy) - .map(is_license_file) - .unwrap_or_default() - }) - .filter_map(|entry| { - let contents = fs::read_to_string(entry).unwrap_or_default(); - self.analyze(&contents) - }) - .collect::>(); + let mut output = fs::read_dir(dir)? + .filter_map(std::result::Result::ok) + .map(|entry| entry.path()) + .filter(|entry| { + entry.is_file() + && entry + .file_name() + .map(OsStr::to_string_lossy) + .map(is_license_file) + .unwrap_or_default() + }) + .filter_map(|entry| { + let contents = fs::read_to_string(entry).unwrap_or_default(); + self.analyze(&contents) + }) + .collect::>(); - output.sort(); - output.dedup(); - Ok(output.join(", ")) - } + output.sort(); + output.dedup(); + Ok(output.join(", ")) + } - fn analyze(&self, text: &str) -> Option { - let matched = self.store.analyze(&TextData::from(text)); + fn analyze(&self, text: &str) -> Option { + let matched = self.store.analyze(&TextData::from(text)); - if matched.score >= MIN_THRESHOLD { - Some(matched.name.into()) - } else { - None - } - } + if matched.score >= MIN_THRESHOLD { + Some(matched.name.into()) + } else { + None + } + } } diff --git a/src/info/mod.rs b/src/info/mod.rs index 9ee3629ce..34eba5111 100644 --- a/src/info/mod.rs +++ b/src/info/mod.rs @@ -22,377 +22,377 @@ mod license; pub mod repo; pub struct Info { - git_username: String, - git_version: String, - repo_name: String, - number_of_tags: usize, - number_of_branches: usize, - head_refs: HeadRefs, - pending_changes: String, - version: String, - creation_date: String, - languages: Vec<(Language, f64)>, - dependencies: String, - authors: Vec, - last_change: String, - contributors: usize, - repo_url: String, - number_of_commits: String, - lines_of_code: usize, - file_count: u64, - repo_size: String, - license: String, - pub dominant_language: Language, - pub ascii_colors: Vec, - pub text_colors: TextColor, - pub config: Config, + git_username: String, + git_version: String, + repo_name: String, + number_of_tags: usize, + number_of_branches: usize, + head_refs: HeadRefs, + pending_changes: String, + version: String, + creation_date: String, + languages: Vec<(Language, f64)>, + dependencies: String, + authors: Vec, + last_change: String, + contributors: usize, + repo_url: String, + number_of_commits: String, + lines_of_code: usize, + file_count: u64, + repo_size: String, + license: String, + pub dominant_language: Language, + pub ascii_colors: Vec, + pub text_colors: TextColor, + pub config: Config, } impl std::fmt::Display for Info { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - if !self.config.disabled_fields.git_info - && (!&self.git_username.is_empty() || !&self.git_version.is_empty()) - { - let (git_info_field_str, git_info_field_len) = self.get_git_info_field(); - writeln!(f, "{}", git_info_field_str)?; - let separator = "-".repeat(git_info_field_len); - writeln!(f, "{}", separator.color(self.text_colors.underline))?; - } - - if !self.config.disabled_fields.project && !self.repo_name.is_empty() { - let branches_tags_str = self.get_branches_and_tags_field(); - let project_str = format!("{} {}", &self.repo_name, branches_tags_str); - self.write_colored_info_line("Project", &project_str, f)?; - } - - if !self.config.disabled_fields.head { - self.write_colored_info_line("HEAD", &self.head_refs.to_string(), f)?; - } - - if !self.config.disabled_fields.pending && !self.pending_changes.is_empty() { - self.write_colored_info_line("Pending", &self.pending_changes, f)?; - } - - if !self.config.disabled_fields.version && !self.version.is_empty() { - self.write_colored_info_line("Version", &self.version, f)?; - } - - if !self.config.disabled_fields.created && !self.creation_date.is_empty() { - self.write_colored_info_line("Created", &self.creation_date, f)?; - } - - if !self.config.disabled_fields.languages && !self.languages.is_empty() { - let title = if self.languages.len() > 1 { - "Languages" - } else { - "Language" - }; - let languages_str = self.get_language_field(title); - self.write_info_line(title, &languages_str, f)?; - } - - if !self.config.disabled_fields.dependencies && !self.dependencies.is_empty() { - self.write_colored_info_line("Dependencies", &self.dependencies, f)?; - } - - if !self.config.disabled_fields.authors && !self.authors.is_empty() { - let title = if self.authors.len() > 1 { - "Authors" - } else { - "Author" - }; - let author_str = self.get_author_field(title); - self.write_info_line(title, &author_str, f)?; - } - - if !self.config.disabled_fields.last_change && !self.last_change.is_empty() { - self.write_colored_info_line("Last change", &self.last_change, f)?; - } - - if !self.config.disabled_fields.contributors - && self.contributors > self.config.number_of_authors - { - self.write_colored_info_line("Contributors", &self.contributors.to_string(), f)?; - } - - if !self.config.disabled_fields.repo && !self.repo_url.is_empty() { - self.write_colored_info_line("Repo", &self.repo_url, f)?; - } - - if !self.config.disabled_fields.commits { - self.write_colored_info_line("Commits", &self.number_of_commits, f)?; - } - - if !self.config.disabled_fields.lines_of_code { - self.write_colored_info_line("Lines of code", &self.lines_of_code.to_string(), f)?; - } - - if !self.config.disabled_fields.size && !self.repo_size.is_empty() { - let repo_size_str = self.get_repo_size_field(); - self.write_colored_info_line("Size", &repo_size_str, f)?; - } - - if !self.config.disabled_fields.license && !self.license.is_empty() { - self.write_colored_info_line("License", &self.license, f)?; - } - - if !self.config.no_color_palette { - writeln!( - f, - "\n{0}{1}{2}{3}{4}{5}{6}{7}", - " ".on_black(), - " ".on_red(), - " ".on_green(), - " ".on_yellow(), - " ".on_blue(), - " ".on_magenta(), - " ".on_cyan(), - " ".on_white() - )?; - } - - Ok(()) - } + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if !self.config.disabled_fields.git_info + && (!&self.git_username.is_empty() || !&self.git_version.is_empty()) + { + let (git_info_field_str, git_info_field_len) = self.get_git_info_field(); + writeln!(f, "{}", git_info_field_str)?; + let separator = "-".repeat(git_info_field_len); + writeln!(f, "{}", separator.color(self.text_colors.underline))?; + } + + if !self.config.disabled_fields.project && !self.repo_name.is_empty() { + let branches_tags_str = self.get_branches_and_tags_field(); + let project_str = format!("{} {}", &self.repo_name, branches_tags_str); + self.write_colored_info_line("Project", &project_str, f)?; + } + + if !self.config.disabled_fields.head { + self.write_colored_info_line("HEAD", &self.head_refs.to_string(), f)?; + } + + if !self.config.disabled_fields.pending && !self.pending_changes.is_empty() { + self.write_colored_info_line("Pending", &self.pending_changes, f)?; + } + + if !self.config.disabled_fields.version && !self.version.is_empty() { + self.write_colored_info_line("Version", &self.version, f)?; + } + + if !self.config.disabled_fields.created && !self.creation_date.is_empty() { + self.write_colored_info_line("Created", &self.creation_date, f)?; + } + + if !self.config.disabled_fields.languages && !self.languages.is_empty() { + let title = if self.languages.len() > 1 { + "Languages" + } else { + "Language" + }; + let languages_str = self.get_language_field(title); + self.write_info_line(title, &languages_str, f)?; + } + + if !self.config.disabled_fields.dependencies && !self.dependencies.is_empty() { + self.write_colored_info_line("Dependencies", &self.dependencies, f)?; + } + + if !self.config.disabled_fields.authors && !self.authors.is_empty() { + let title = if self.authors.len() > 1 { + "Authors" + } else { + "Author" + }; + let author_str = self.get_author_field(title); + self.write_info_line(title, &author_str, f)?; + } + + if !self.config.disabled_fields.last_change && !self.last_change.is_empty() { + self.write_colored_info_line("Last change", &self.last_change, f)?; + } + + if !self.config.disabled_fields.contributors + && self.contributors > self.config.number_of_authors + { + self.write_colored_info_line("Contributors", &self.contributors.to_string(), f)?; + } + + if !self.config.disabled_fields.repo && !self.repo_url.is_empty() { + self.write_colored_info_line("Repo", &self.repo_url, f)?; + } + + if !self.config.disabled_fields.commits { + self.write_colored_info_line("Commits", &self.number_of_commits, f)?; + } + + if !self.config.disabled_fields.lines_of_code { + self.write_colored_info_line("Lines of code", &self.lines_of_code.to_string(), f)?; + } + + if !self.config.disabled_fields.size && !self.repo_size.is_empty() { + let repo_size_str = self.get_repo_size_field(); + self.write_colored_info_line("Size", &repo_size_str, f)?; + } + + if !self.config.disabled_fields.license && !self.license.is_empty() { + self.write_colored_info_line("License", &self.license, f)?; + } + + if !self.config.no_color_palette { + writeln!( + f, + "\n{0}{1}{2}{3}{4}{5}{6}{7}", + " ".on_black(), + " ".on_red(), + " ".on_green(), + " ".on_yellow(), + " ".on_blue(), + " ".on_magenta(), + " ".on_cyan(), + " ".on_white() + )?; + } + + Ok(()) + } } impl Info { - pub fn new(config: Config) -> Result { - let git_version = cli::get_git_version(); - let repo = Repository::discover(&config.repo_path)?; - let internal_repo = Repo::new(&repo, config.no_merges, &config.bot_regex_pattern)?; - let (repo_name, repo_url) = internal_repo.get_name_and_url()?; - let head_refs = internal_repo.get_head_refs()?; - let pending_changes = internal_repo.get_pending_changes()?; - let version = internal_repo.get_version()?; - 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(config.iso_time)?; - let number_of_commits = internal_repo.get_number_of_commits(); - let (authors, contributors) = - internal_repo.get_authors(config.number_of_authors, config.show_email)?; - 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)?; - let dependencies = DependencyDetector::new().get_dependencies(&workdir)?; - let (languages, lines_of_code) = langs::get_language_statistics( - &workdir, - &config.ignored_directories, - &config.language_types, - config.include_hidden, - )?; - let dominant_language = langs::get_dominant_language(&languages); - let ascii_colors = get_ascii_colors( - &config.ascii_language, - &dominant_language, - &config.ascii_colors, - config.true_color, - ); - let text_colors = TextColor::get_text_colors(&config.text_colors, &ascii_colors); - - Ok(Self { - git_username, - git_version, - repo_name, - number_of_tags, - number_of_branches, - head_refs, - pending_changes, - version, - creation_date, - languages, - dependencies, - authors, - last_change, - contributors, - repo_url, - number_of_commits, - lines_of_code, - file_count, - repo_size, - license, - dominant_language, - ascii_colors, - text_colors, - config, - }) - } - - fn write_colored_info_line( - &self, - label: &str, - info: &str, - f: &mut std::fmt::Formatter, - ) -> std::fmt::Result { - let info_colored = info.color(self.text_colors.info); - writeln!( - f, - "{} {}", - &self.get_formatted_subtitle_label(label), - info_colored - ) - } - - fn write_info_line( - &self, - label: &str, - info: &str, - f: &mut std::fmt::Formatter, - ) -> std::fmt::Result { - writeln!(f, "{} {}", &self.get_formatted_subtitle_label(label), info) - } - - fn get_formatted_subtitle_label(&self, label: &str) -> ColoredString { - let formatted_label = format!( - "{}{}", - label.color(self.text_colors.subtitle), - ":".color(self.text_colors.colon) - ); - self.bold(&formatted_label) - } - - fn bold(&self, label: &str) -> ColoredString { - if self.config.no_bold { - label.normal() - } else { - label.bold() - } - } - - fn get_git_info_field(&self) -> (String, usize) { - let git_info_length = self.git_username.len() + self.git_version.len(); - - if !&self.git_username.is_empty() && !&self.git_version.is_empty() { - ( - format!( - "{} {} {}", - &self.bold(&self.git_username).color(self.text_colors.title), - &self.bold("~").color(self.text_colors.tilde), - &self.bold(&self.git_version).color(self.text_colors.title) - ), - git_info_length + 3, - ) - } else { - ( - format!( - "{}{}", - &self.bold(&self.git_username).color(self.text_colors.title), - &self.bold(&self.git_version).color(self.text_colors.title) - ), - git_info_length, - ) - } - } - - fn get_author_field(&self, title: &str) -> String { - let mut author_field = String::from(""); - - let pad = title.len() + 2; - - for (i, author) in self.authors.iter().enumerate() { - let author_str = format!("{}", author).color(self.text_colors.info); - - if i == 0 { - author_field.push_str(&format!("{}", author_str)); - } else { - author_field.push_str(&format!("\n{: String { - let mut language_field = String::from(""); - - let pad = title.len() + 2; - - let languages: Vec<(String, f64)> = { - let mut iter = self.languages.iter().map(|x| (format!("{}", x.0), x.1)); - if self.languages.len() > 6 { - let mut languages = iter.by_ref().take(6).collect::>(); - let other_sum = iter.fold(0.0, |acc, x| acc + x.1); - languages.push(("Other".to_owned(), other_sum)); - languages - } else { - iter.collect() - } - }; - - for (cnt, language) in languages.iter().enumerate() { - let formatted_number = format!("{:.*}", 1, language.1); - let language_str = - format!("{} ({} %) ", language.0, formatted_number).color(self.text_colors.info); - if cnt != 0 && cnt % 2 == 0 { - language_field.push_str(&format!("\n{: String { - let branches_str = match self.number_of_branches { - 0 => String::new(), - 1 => String::from("1 branch"), - _ => format!("{} branches", self.number_of_branches), - }; - - let tags_str = match self.number_of_tags { - 0 => String::new(), - 1 => String::from("1 tag"), - _ => format!("{} tags", self.number_of_tags), - }; - - if tags_str.is_empty() && branches_str.is_empty() { - String::new() - } else if branches_str.is_empty() || tags_str.is_empty() { - format!("({}{})", tags_str, branches_str) - } else { - format!("({}, {})", branches_str, tags_str) - } - } - - fn get_repo_size_field(&self) -> String { - match self.file_count { - 0 => String::from(&self.repo_size), - _ => { - let res = format!("{} ({} files)", self.repo_size, self.file_count.to_string()); - res - } - } - } + pub fn new(config: Config) -> Result { + let git_version = cli::get_git_version(); + let repo = Repository::discover(&config.repo_path)?; + let internal_repo = Repo::new(&repo, config.no_merges, &config.bot_regex_pattern)?; + let (repo_name, repo_url) = internal_repo.get_name_and_url()?; + let head_refs = internal_repo.get_head_refs()?; + let pending_changes = internal_repo.get_pending_changes()?; + let version = internal_repo.get_version()?; + 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(config.iso_time)?; + let number_of_commits = internal_repo.get_number_of_commits(); + let (authors, contributors) = + internal_repo.get_authors(config.number_of_authors, config.show_email)?; + 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)?; + let dependencies = DependencyDetector::new().get_dependencies(&workdir)?; + let (languages, lines_of_code) = langs::get_language_statistics( + &workdir, + &config.ignored_directories, + &config.language_types, + config.include_hidden, + )?; + let dominant_language = langs::get_dominant_language(&languages); + let ascii_colors = get_ascii_colors( + &config.ascii_language, + &dominant_language, + &config.ascii_colors, + config.true_color, + ); + let text_colors = TextColor::get_text_colors(&config.text_colors, &ascii_colors); + + Ok(Self { + git_username, + git_version, + repo_name, + number_of_tags, + number_of_branches, + head_refs, + pending_changes, + version, + creation_date, + languages, + dependencies, + authors, + last_change, + contributors, + repo_url, + number_of_commits, + lines_of_code, + file_count, + repo_size, + license, + dominant_language, + ascii_colors, + text_colors, + config, + }) + } + + fn write_colored_info_line( + &self, + label: &str, + info: &str, + f: &mut std::fmt::Formatter, + ) -> std::fmt::Result { + let info_colored = info.color(self.text_colors.info); + writeln!( + f, + "{} {}", + &self.get_formatted_subtitle_label(label), + info_colored + ) + } + + fn write_info_line( + &self, + label: &str, + info: &str, + f: &mut std::fmt::Formatter, + ) -> std::fmt::Result { + writeln!(f, "{} {}", &self.get_formatted_subtitle_label(label), info) + } + + fn get_formatted_subtitle_label(&self, label: &str) -> ColoredString { + let formatted_label = format!( + "{}{}", + label.color(self.text_colors.subtitle), + ":".color(self.text_colors.colon) + ); + self.bold(&formatted_label) + } + + fn bold(&self, label: &str) -> ColoredString { + if self.config.no_bold { + label.normal() + } else { + label.bold() + } + } + + fn get_git_info_field(&self) -> (String, usize) { + let git_info_length = self.git_username.len() + self.git_version.len(); + + if !&self.git_username.is_empty() && !&self.git_version.is_empty() { + ( + format!( + "{} {} {}", + &self.bold(&self.git_username).color(self.text_colors.title), + &self.bold("~").color(self.text_colors.tilde), + &self.bold(&self.git_version).color(self.text_colors.title) + ), + git_info_length + 3, + ) + } else { + ( + format!( + "{}{}", + &self.bold(&self.git_username).color(self.text_colors.title), + &self.bold(&self.git_version).color(self.text_colors.title) + ), + git_info_length, + ) + } + } + + fn get_author_field(&self, title: &str) -> String { + let mut author_field = String::from(""); + + let pad = title.len() + 2; + + for (i, author) in self.authors.iter().enumerate() { + let author_str = format!("{}", author).color(self.text_colors.info); + + if i == 0 { + author_field.push_str(&format!("{}", author_str)); + } else { + author_field.push_str(&format!("\n{: String { + let mut language_field = String::from(""); + + let pad = title.len() + 2; + + let languages: Vec<(String, f64)> = { + let mut iter = self.languages.iter().map(|x| (format!("{}", x.0), x.1)); + if self.languages.len() > 6 { + let mut languages = iter.by_ref().take(6).collect::>(); + let other_sum = iter.fold(0.0, |acc, x| acc + x.1); + languages.push(("Other".to_owned(), other_sum)); + languages + } else { + iter.collect() + } + }; + + for (cnt, language) in languages.iter().enumerate() { + let formatted_number = format!("{:.*}", 1, language.1); + let language_str = + format!("{} ({} %) ", language.0, formatted_number).color(self.text_colors.info); + if cnt != 0 && cnt % 2 == 0 { + language_field.push_str(&format!("\n{: String { + let branches_str = match self.number_of_branches { + 0 => String::new(), + 1 => String::from("1 branch"), + _ => format!("{} branches", self.number_of_branches), + }; + + let tags_str = match self.number_of_tags { + 0 => String::new(), + 1 => String::from("1 tag"), + _ => format!("{} tags", self.number_of_tags), + }; + + if tags_str.is_empty() && branches_str.is_empty() { + String::new() + } else if branches_str.is_empty() || tags_str.is_empty() { + format!("({}{})", tags_str, branches_str) + } else { + format!("({}, {})", branches_str, tags_str) + } + } + + fn get_repo_size_field(&self) -> String { + match self.file_count { + 0 => String::from(&self.repo_size), + _ => { + let res = format!("{} ({} files)", self.repo_size, self.file_count.to_string()); + res + } + } + } } impl Serialize for Info { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_struct("Info", 15)?; - let langs: Vec = self - .languages - .iter() - .map(|(l, _)| format!("{}", l)) - .collect(); - state.serialize_field("repoName", &self.repo_name)?; - state.serialize_field("numberOfTags", &self.number_of_tags)?; - state.serialize_field("numberOfBranches", &self.number_of_branches)?; - state.serialize_field("headRefs", &self.head_refs)?; - state.serialize_field("version", &self.version)?; - state.serialize_field("creationDate", &self.creation_date)?; - state.serialize_field("languages", &langs)?; - state.serialize_field("authors", &self.authors)?; - state.serialize_field("lastChange", &self.last_change)?; - state.serialize_field("repoUrl", &self.repo_url)?; - state.serialize_field("numberOfCommits", &self.number_of_commits)?; - state.serialize_field("linesOfCode", &self.lines_of_code)?; - state.serialize_field("repoSize", &self.repo_size)?; - state.serialize_field("filesCount", &self.file_count)?; - state.serialize_field("license", &self.license)?; - - state.end() - } + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("Info", 15)?; + let langs: Vec = self + .languages + .iter() + .map(|(l, _)| format!("{}", l)) + .collect(); + state.serialize_field("repoName", &self.repo_name)?; + state.serialize_field("numberOfTags", &self.number_of_tags)?; + state.serialize_field("numberOfBranches", &self.number_of_branches)?; + state.serialize_field("headRefs", &self.head_refs)?; + state.serialize_field("version", &self.version)?; + state.serialize_field("creationDate", &self.creation_date)?; + state.serialize_field("languages", &langs)?; + state.serialize_field("authors", &self.authors)?; + state.serialize_field("lastChange", &self.last_change)?; + state.serialize_field("repoUrl", &self.repo_url)?; + state.serialize_field("numberOfCommits", &self.number_of_commits)?; + state.serialize_field("linesOfCode", &self.lines_of_code)?; + state.serialize_field("repoSize", &self.repo_size)?; + state.serialize_field("filesCount", &self.file_count)?; + state.serialize_field("license", &self.license)?; + + state.end() + } } diff --git a/src/info/repo.rs b/src/info/repo.rs index 0323b42a7..a544cca76 100644 --- a/src/info/repo.rs +++ b/src/info/repo.rs @@ -6,355 +6,355 @@ use chrono::{FixedOffset, TimeZone}; use chrono_humanize::HumanTime; use git2::Time; use git2::{ - BranchType, Commit, Repository, RepositoryOpenFlags, Signature, Status, StatusOptions, - StatusShow, + BranchType, Commit, Repository, RepositoryOpenFlags, Signature, Status, StatusOptions, + StatusShow, }; use regex::Regex; use std::collections::HashMap; use std::path::Path; pub struct Repo<'a> { - repo: &'a Repository, - logs: Vec>, + repo: &'a Repository, + logs: Vec>, } #[derive(Hash, PartialEq, Eq)] pub struct Sig { - name: String, - email: String, + name: String, + email: String, } impl From> for Sig { - fn from(sig: Signature) -> Self { - let name = String::from_utf8_lossy(sig.name_bytes()).into_owned(); - let email = String::from_utf8_lossy(sig.email_bytes()).into_owned(); - Self { name, email } - } + fn from(sig: Signature) -> Self { + let name = String::from_utf8_lossy(sig.name_bytes()).into_owned(); + let email = String::from_utf8_lossy(sig.email_bytes()).into_owned(); + Self { name, email } + } } impl<'a> Repo<'a> { - pub fn new( - repo: &'a Repository, - no_merges: bool, - bot_regex_pattern: &Option, - ) -> Result { - let logs = Self::get_logs(repo, no_merges, bot_regex_pattern)?; - Ok(Self { repo, logs }) - } - - fn get_logs( - repo: &'a Repository, - no_merges: bool, - bot_regex_pattern: &Option, - ) -> Result>> { - let mut revwalk = repo.revwalk()?; - revwalk.push_head()?; - let logs: Vec> = revwalk - .filter_map(|r| match r { - Err(_) => None, - Ok(r) => repo - .find_commit(r) - .ok() - .filter(|commit| !(no_merges && commit.parents().len() > 1)) - .filter(|commit| { - !(bot_regex_pattern.is_some() && is_bot(commit.author(), bot_regex_pattern)) - }), - }) - .collect(); - - Ok(logs) - } - - 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(); - git_time_to_formatted_time(&time, iso_time) - } - None => "".into(), - }; - - Ok(output) - } - - pub fn get_number_of_commits(&self) -> String { - let number_of_commits = self.logs.len(); - number_of_commits.to_string() - } - - pub fn get_authors( - &self, - number_of_authors_to_display: usize, - show_email: bool, - ) -> Result<(Vec, usize)> { - let mut author_to_number_of_commits: HashMap = HashMap::new(); - let mut total_nbr_of_commits = 0; - let mailmap = self.repo.mailmap()?; - for commit in &self.logs { - let author = match commit.author_with_mailmap(&mailmap) { - Ok(val) => val, - Err(_) => commit.author(), - }; - let author_nbr_of_commits = author_to_number_of_commits - .entry(Sig::from(author)) - .or_insert(0); - *author_nbr_of_commits += 1; - total_nbr_of_commits += 1; - } - - let mut authors_by_number_of_commits: Vec<(Sig, usize)> = - author_to_number_of_commits.into_iter().collect(); - - let number_of_authors = authors_by_number_of_commits.len(); - - authors_by_number_of_commits.sort_by(|(_, a_count), (_, b_count)| b_count.cmp(a_count)); - - if number_of_authors > number_of_authors_to_display { - authors_by_number_of_commits.truncate(number_of_authors_to_display); - } - - let authors: Vec = authors_by_number_of_commits - .into_iter() - .map(|(author, author_nbr_of_commits)| { - Author::new( - author.name.clone(), - show_email.then(|| author.email), - author_nbr_of_commits, - total_nbr_of_commits, - ) - }) - .collect(); - - Ok((authors, number_of_authors)) - } - - pub fn get_date_of_last_commit(&self, iso_time: bool) -> String { - let last_commit = self.logs.first(); - - match last_commit { - Some(commit) => git_time_to_formatted_time(&commit.time(), iso_time), - None => "".into(), - } - } - - // This collects the repo size excluding .git - pub fn get_repo_size(&self) -> (String, u64) { - let (repo_size, file_count) = match self.repo.index() { - Ok(index) => index.iter().fold( - (0, 0), - |(repo_size, file_count): (u128, u64), index_entry| -> (u128, u64) { - (repo_size + index_entry.file_size as u128, file_count + 1) - }, - ), - Err(_) => (0, 0), - }; - - (bytes_to_human_readable(repo_size), file_count) - } - - pub fn get_work_dir(&self) -> Result { - let workdir = self - .work_dir()? - .to_str() - .with_context(|| "invalid workdir")?; - Ok(workdir.to_string()) - } - - pub fn get_number_of_tags(&self) -> Result { - Ok(self.repo.tag_names(None)?.len()) - } - - pub fn get_number_of_branches(&self) -> Result { - let mut number_of_branches = self.repo.branches(Some(BranchType::Remote))?.count(); - if number_of_branches > 0 { - //Exclude origin/HEAD -> origin/main - number_of_branches -= 1; - } - Ok(number_of_branches) - } - - pub fn get_git_username(&self) -> Result { - let config = self.repo.config()?; - let username = match config.get_entry("user.name") { - Ok(v) => v.value().unwrap_or("").into(), - Err(_) => "".into(), - }; - - Ok(username) - } - - pub fn get_version(&self) -> Result { - let mut version_name = String::new(); - let mut most_recent: i64 = 0; - - self.repo.tag_foreach(|id, name| { - if let Ok(name) = String::from_utf8(name[10..].into()) { - let mut current_time: i64 = 0; - if let Ok(tag) = self.repo.find_tag(id) { - if let Ok(c) = self.repo.find_commit(tag.target_id()) { - current_time = c.time().seconds(); - } - } else if let Ok(c) = self.repo.find_commit(id) { - current_time = c.time().seconds(); - } - if current_time > most_recent { - most_recent = current_time; - version_name = name; - } - - return true; - } - false - })?; - - Ok(version_name) - } - - pub fn get_pending_changes(&self) -> Result { - let statuses = self.repo.statuses(Some( - StatusOptions::default() - .show(StatusShow::Workdir) - .update_index(true) - .include_untracked(true) - .renames_head_to_index(true) - .recurse_untracked_dirs(true), - ))?; - - let (added, deleted, modified) = - statuses - .iter() - .fold((0, 0, 0), |(added, deleted, modified), e| { - let s: Status = e.status(); - if s.is_index_new() || s.is_wt_new() { - (added + 1, deleted, modified) - } else if s.is_index_deleted() || s.is_wt_deleted() { - (added, deleted + 1, modified) - } else { - (added, deleted, modified + 1) - } - }); - - let mut result = String::new(); - if modified > 0 { - result = format!("{}+-", modified) - } - - if added > 0 { - result = format!("{} {}+", result, added); - } - - if deleted > 0 { - result = format!("{} {}-", result, deleted); - } - - Ok(result.trim().into()) - } - - pub fn get_name_and_url(&self) -> Result<(String, String)> { - let config = self.repo.config()?; - let mut remote_origin_url: Option = None; - let mut remote_url_fallback = String::new(); - let mut repository_name = String::new(); - let remote_regex = Regex::new(r"remote\.[a-zA-Z0-9]+\.url")?; - - for entry in &config.entries(None)? { - let entry = entry?; - let entry_name = entry.name().with_context(|| "Could not read entry name")?; - if entry_name == "remote.origin.url" { - remote_origin_url = Some( - entry - .value() - .with_context(|| "Could not read remote origin url")? - .to_string(), - ); - } else if remote_regex.is_match(entry_name) { - remote_url_fallback = entry - .value() - .with_context(|| "Could not read remote origin url fallback")? - .to_string() - } - } - - let remote_url = if let Some(url) = remote_origin_url { - url - } else { - remote_url_fallback - }; - - let name_parts: Vec<&str> = remote_url.split('/').collect(); - - if !name_parts.is_empty() { - let mut i = 1; - while repository_name.is_empty() && i <= name_parts.len() { - repository_name = name_parts[name_parts.len() - i].to_string(); - i += 1; - } - } - - if repository_name.contains(".git") { - let repo_name = repository_name.clone(); - let parts: Vec<&str> = repo_name.split(".git").collect(); - repository_name = parts[0].to_string(); - } - - Ok((repository_name, remote_url)) - } - - pub fn get_head_refs(&self) -> Result { - let head = self.repo.head()?; - let head_oid = head.target().with_context(|| "Could not read HEAD")?; - let refs = self.repo.references()?; - let refs_info = refs - .filter_map(|reference| match reference { - Ok(reference) => match (reference.target(), reference.shorthand()) { - (Some(oid), Some(shorthand)) if oid == head_oid && !reference.is_tag() => { - Some(String::from(shorthand)) - } - _ => None, - }, - Err(_) => None, - }) - .collect::>(); - Ok(HeadRefs::new(head_oid, refs_info)) - } - - fn work_dir(&self) -> Result<&Path> { - self.repo - .workdir() - .with_context(|| "unable to query workdir") - } + pub fn new( + repo: &'a Repository, + no_merges: bool, + bot_regex_pattern: &Option, + ) -> Result { + let logs = Self::get_logs(repo, no_merges, bot_regex_pattern)?; + Ok(Self { repo, logs }) + } + + fn get_logs( + repo: &'a Repository, + no_merges: bool, + bot_regex_pattern: &Option, + ) -> Result>> { + let mut revwalk = repo.revwalk()?; + revwalk.push_head()?; + let logs: Vec> = revwalk + .filter_map(|r| match r { + Err(_) => None, + Ok(r) => repo + .find_commit(r) + .ok() + .filter(|commit| !(no_merges && commit.parents().len() > 1)) + .filter(|commit| { + !(bot_regex_pattern.is_some() && is_bot(commit.author(), bot_regex_pattern)) + }), + }) + .collect(); + + Ok(logs) + } + + 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(); + git_time_to_formatted_time(&time, iso_time) + } + None => "".into(), + }; + + Ok(output) + } + + pub fn get_number_of_commits(&self) -> String { + let number_of_commits = self.logs.len(); + number_of_commits.to_string() + } + + pub fn get_authors( + &self, + number_of_authors_to_display: usize, + show_email: bool, + ) -> Result<(Vec, usize)> { + let mut author_to_number_of_commits: HashMap = HashMap::new(); + let mut total_nbr_of_commits = 0; + let mailmap = self.repo.mailmap()?; + for commit in &self.logs { + let author = match commit.author_with_mailmap(&mailmap) { + Ok(val) => val, + Err(_) => commit.author(), + }; + let author_nbr_of_commits = author_to_number_of_commits + .entry(Sig::from(author)) + .or_insert(0); + *author_nbr_of_commits += 1; + total_nbr_of_commits += 1; + } + + let mut authors_by_number_of_commits: Vec<(Sig, usize)> = + author_to_number_of_commits.into_iter().collect(); + + let number_of_authors = authors_by_number_of_commits.len(); + + authors_by_number_of_commits.sort_by(|(_, a_count), (_, b_count)| b_count.cmp(a_count)); + + if number_of_authors > number_of_authors_to_display { + authors_by_number_of_commits.truncate(number_of_authors_to_display); + } + + let authors: Vec = authors_by_number_of_commits + .into_iter() + .map(|(author, author_nbr_of_commits)| { + Author::new( + author.name.clone(), + show_email.then(|| author.email), + author_nbr_of_commits, + total_nbr_of_commits, + ) + }) + .collect(); + + Ok((authors, number_of_authors)) + } + + pub fn get_date_of_last_commit(&self, iso_time: bool) -> String { + let last_commit = self.logs.first(); + + match last_commit { + Some(commit) => git_time_to_formatted_time(&commit.time(), iso_time), + None => "".into(), + } + } + + // This collects the repo size excluding .git + pub fn get_repo_size(&self) -> (String, u64) { + let (repo_size, file_count) = match self.repo.index() { + Ok(index) => index.iter().fold( + (0, 0), + |(repo_size, file_count): (u128, u64), index_entry| -> (u128, u64) { + (repo_size + index_entry.file_size as u128, file_count + 1) + }, + ), + Err(_) => (0, 0), + }; + + (bytes_to_human_readable(repo_size), file_count) + } + + pub fn get_work_dir(&self) -> Result { + let workdir = self + .work_dir()? + .to_str() + .with_context(|| "invalid workdir")?; + Ok(workdir.to_string()) + } + + pub fn get_number_of_tags(&self) -> Result { + Ok(self.repo.tag_names(None)?.len()) + } + + pub fn get_number_of_branches(&self) -> Result { + let mut number_of_branches = self.repo.branches(Some(BranchType::Remote))?.count(); + if number_of_branches > 0 { + //Exclude origin/HEAD -> origin/main + number_of_branches -= 1; + } + Ok(number_of_branches) + } + + pub fn get_git_username(&self) -> Result { + let config = self.repo.config()?; + let username = match config.get_entry("user.name") { + Ok(v) => v.value().unwrap_or("").into(), + Err(_) => "".into(), + }; + + Ok(username) + } + + pub fn get_version(&self) -> Result { + let mut version_name = String::new(); + let mut most_recent: i64 = 0; + + self.repo.tag_foreach(|id, name| { + if let Ok(name) = String::from_utf8(name[10..].into()) { + let mut current_time: i64 = 0; + if let Ok(tag) = self.repo.find_tag(id) { + if let Ok(c) = self.repo.find_commit(tag.target_id()) { + current_time = c.time().seconds(); + } + } else if let Ok(c) = self.repo.find_commit(id) { + current_time = c.time().seconds(); + } + if current_time > most_recent { + most_recent = current_time; + version_name = name; + } + + return true; + } + false + })?; + + Ok(version_name) + } + + pub fn get_pending_changes(&self) -> Result { + let statuses = self.repo.statuses(Some( + StatusOptions::default() + .show(StatusShow::Workdir) + .update_index(true) + .include_untracked(true) + .renames_head_to_index(true) + .recurse_untracked_dirs(true), + ))?; + + let (added, deleted, modified) = + statuses + .iter() + .fold((0, 0, 0), |(added, deleted, modified), e| { + let s: Status = e.status(); + if s.is_index_new() || s.is_wt_new() { + (added + 1, deleted, modified) + } else if s.is_index_deleted() || s.is_wt_deleted() { + (added, deleted + 1, modified) + } else { + (added, deleted, modified + 1) + } + }); + + let mut result = String::new(); + if modified > 0 { + result = format!("{}+-", modified) + } + + if added > 0 { + result = format!("{} {}+", result, added); + } + + if deleted > 0 { + result = format!("{} {}-", result, deleted); + } + + Ok(result.trim().into()) + } + + pub fn get_name_and_url(&self) -> Result<(String, String)> { + let config = self.repo.config()?; + let mut remote_origin_url: Option = None; + let mut remote_url_fallback = String::new(); + let mut repository_name = String::new(); + let remote_regex = Regex::new(r"remote\.[a-zA-Z0-9]+\.url")?; + + for entry in &config.entries(None)? { + let entry = entry?; + let entry_name = entry.name().with_context(|| "Could not read entry name")?; + if entry_name == "remote.origin.url" { + remote_origin_url = Some( + entry + .value() + .with_context(|| "Could not read remote origin url")? + .to_string(), + ); + } else if remote_regex.is_match(entry_name) { + remote_url_fallback = entry + .value() + .with_context(|| "Could not read remote origin url fallback")? + .to_string() + } + } + + let remote_url = if let Some(url) = remote_origin_url { + url + } else { + remote_url_fallback + }; + + let name_parts: Vec<&str> = remote_url.split('/').collect(); + + if !name_parts.is_empty() { + let mut i = 1; + while repository_name.is_empty() && i <= name_parts.len() { + repository_name = name_parts[name_parts.len() - i].to_string(); + i += 1; + } + } + + if repository_name.contains(".git") { + let repo_name = repository_name.clone(); + let parts: Vec<&str> = repo_name.split(".git").collect(); + repository_name = parts[0].to_string(); + } + + Ok((repository_name, remote_url)) + } + + pub fn get_head_refs(&self) -> Result { + let head = self.repo.head()?; + let head_oid = head.target().with_context(|| "Could not read HEAD")?; + let refs = self.repo.references()?; + let refs_info = refs + .filter_map(|reference| match reference { + Ok(reference) => match (reference.target(), reference.shorthand()) { + (Some(oid), Some(shorthand)) if oid == head_oid && !reference.is_tag() => { + Some(String::from(shorthand)) + } + _ => None, + }, + Err(_) => None, + }) + .collect::>(); + Ok(HeadRefs::new(head_oid, refs_info)) + } + + fn work_dir(&self) -> Result<&Path> { + self.repo + .workdir() + .with_context(|| "unable to query workdir") + } } fn is_bot(author: Signature, bot_regex_pattern: &Option) -> bool { - let author_name = String::from_utf8_lossy(author.name_bytes()).into_owned(); - bot_regex_pattern.as_ref().unwrap().is_match(&author_name) + let author_name = String::from_utf8_lossy(author.name_bytes()).into_owned(); + bot_regex_pattern.as_ref().unwrap().is_match(&author_name) } fn bytes_to_human_readable(bytes: u128) -> String { - let byte = Byte::from_bytes(bytes); - byte.get_appropriate_unit(true).to_string() + let byte = Byte::from_bytes(bytes); + byte.get_appropriate_unit(true).to_string() } 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, '+'), - }; - - let offset = FixedOffset::west(offset); - let dt_with_tz = offset.timestamp(time.seconds(), 0); - 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); - format!("{}", ht) - } + let (offset, _) = match time.offset_minutes() { + n if n < 0 => (-n, '-'), + n => (n, '+'), + }; + + let offset = FixedOffset::west(offset); + let dt_with_tz = offset.timestamp(time.seconds(), 0); + 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); + format!("{}", ht) + } } pub fn is_valid(repo_path: &str) -> Result { - let repo = Repository::open_ext(repo_path, RepositoryOpenFlags::empty(), Vec::<&Path>::new()); - Ok(repo.is_ok() && !repo?.is_bare()) + let repo = Repository::open_ext(repo_path, RepositoryOpenFlags::empty(), Vec::<&Path>::new()); + Ok(repo.is_ok() && !repo?.is_bare()) } diff --git a/src/main.rs b/src/main.rs index c43b02953..b9b1ac333 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,28 +11,28 @@ mod info; mod ui; fn main() -> Result<()> { - #[cfg(windows)] - let _ = ansi_term::enable_ansi_support(); + #[cfg(windows)] + let _ = ansi_term::enable_ansi_support(); - let config = Config::new()?; + let config = Config::new()?; - if config.print_languages { - return cli::print_supported_languages(); - } + if config.print_languages { + return cli::print_supported_languages(); + } - if config.print_package_managers { - return cli::print_supported_package_managers(); - } + if config.print_package_managers { + return cli::print_supported_package_managers(); + } - if !repo::is_valid(&config.repo_path)? { - bail!("please run onefetch inside of a non-bare git repository"); - } + if !repo::is_valid(&config.repo_path)? { + bail!("please run onefetch inside of a non-bare git repository"); + } - let info = Info::new(config)?; + let info = Info::new(config)?; - let mut printer = Printer::new(io::BufWriter::new(io::stdout()), info); + let mut printer = Printer::new(io::BufWriter::new(io::stdout()), info); - printer.print()?; + printer.print()?; - Ok(()) + Ok(()) } diff --git a/src/ui/ascii_art.rs b/src/ui/ascii_art.rs index 7c54c22ab..622354ff3 100644 --- a/src/ui/ascii_art.rs +++ b/src/ui/ascii_art.rs @@ -1,204 +1,204 @@ use colored::{Color, Colorize}; pub struct AsciiArt<'a> { - content: Box>, - colors: &'a [Color], - bold: bool, - start: usize, - end: usize, + content: Box>, + colors: &'a [Color], + bold: bool, + start: usize, + end: usize, } impl<'a> AsciiArt<'a> { - pub fn new(input: &'a str, colors: &'a [Color], bold: bool) -> AsciiArt<'a> { - let mut lines: Vec<_> = input.lines().skip_while(|line| line.is_empty()).collect(); - while let Some(line) = lines.last() { - if Tokens(line).is_empty() { - lines.pop(); - } else { - break; - } - } - - let (start, end) = get_min_start_max_end(&lines); - - AsciiArt { - content: Box::new(lines.into_iter()), - colors, - bold, - start, - end, - } - } - pub fn width(&self) -> usize { - assert!(self.end >= self.start); - self.end - self.start - } + pub fn new(input: &'a str, colors: &'a [Color], bold: bool) -> AsciiArt<'a> { + let mut lines: Vec<_> = input.lines().skip_while(|line| line.is_empty()).collect(); + while let Some(line) = lines.last() { + if Tokens(line).is_empty() { + lines.pop(); + } else { + break; + } + } + + let (start, end) = get_min_start_max_end(&lines); + + AsciiArt { + content: Box::new(lines.into_iter()), + colors, + bold, + start, + end, + } + } + pub fn width(&self) -> usize { + assert!(self.end >= self.start); + self.end - self.start + } } pub fn get_min_start_max_end(lines: &[&str]) -> (usize, usize) { - lines - .iter() - .map(|line| { - let line_start = Tokens(line).leading_spaces(); - let line_end = Tokens(line).true_length(); - (line_start, line_end) - }) - .fold((std::usize::MAX, 0), |(acc_s, acc_e), (line_s, line_e)| { - (acc_s.min(line_s), acc_e.max(line_e)) - }) + lines + .iter() + .map(|line| { + let line_start = Tokens(line).leading_spaces(); + let line_end = Tokens(line).true_length(); + (line_start, line_end) + }) + .fold((std::usize::MAX, 0), |(acc_s, acc_e), (line_s, line_e)| { + (acc_s.min(line_s), acc_e.max(line_e)) + }) } /// Produces a series of lines which have been automatically truncated to the /// correct width impl<'a> Iterator for AsciiArt<'a> { - type Item = String; - fn next(&mut self) -> Option { - self.content - .next() - .map(|line| Tokens(line).render(self.colors, self.start, self.end, self.bold)) - } + type Item = String; + fn next(&mut self) -> Option { + self.content + .next() + .map(|line| Tokens(line).render(self.colors, self.start, self.end, self.bold)) + } } #[derive(Clone, Debug, PartialEq, Eq)] enum Token { - Color(u32), - Char(char), - Space, + Color(u32), + Char(char), + Space, } impl std::fmt::Display for Token { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match *self { - Token::Color(c) => write!(f, "{{{}}}", c), - Token::Char(c) => write!(f, "{}", c), - Token::Space => write!(f, " "), - } - } + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + Token::Color(c) => write!(f, "{{{}}}", c), + Token::Char(c) => write!(f, "{}", c), + Token::Space => write!(f, " "), + } + } } impl Token { - fn is_solid(&self) -> bool { - matches!(*self, Token::Char(_)) - } - fn is_space(&self) -> bool { - matches!(*self, Token::Space) - } - fn has_zero_width(&self) -> bool { - matches!(*self, Token::Color(_)) - } + fn is_solid(&self) -> bool { + matches!(*self, Token::Char(_)) + } + fn is_space(&self) -> bool { + matches!(*self, Token::Space) + } + fn has_zero_width(&self) -> bool { + matches!(*self, Token::Color(_)) + } } /// An iterator over tokens found within the *.ascii format. #[derive(Clone, Debug)] struct Tokens<'a>(&'a str); impl<'a> Iterator for Tokens<'a> { - type Item = Token; - fn next(&mut self) -> Option { - let (s, tok) = color_token(self.0) - .or_else(|| space_token(self.0)) - .or_else(|| char_token(self.0))?; - - self.0 = s; - Some(tok) - } + type Item = Token; + fn next(&mut self) -> Option { + let (s, tok) = color_token(self.0) + .or_else(|| space_token(self.0)) + .or_else(|| char_token(self.0))?; + + self.0 = s; + Some(tok) + } } impl<'a> Tokens<'a> { - fn is_empty(&mut self) -> bool { - for token in self { - if token.is_solid() { - return false; - } - } - true - } - fn true_length(&mut self) -> usize { - let mut last_non_space = 0; - let mut last = 0; - for token in self { - if token.has_zero_width() { - continue; - } - last += 1; - if !token.is_space() { - last_non_space = last; - } - } - last_non_space - } - fn leading_spaces(&mut self) -> usize { - self.take_while(|token| !token.is_solid()) - .filter(|token| token.is_space()) - .count() - } - fn truncate(self, mut start: usize, end: usize) -> impl 'a + Iterator { - assert!(start <= end); - let mut width = end - start; - - self.filter(move |token| { - if start > 0 && !token.has_zero_width() { - start -= 1; - return false; - } - true - }) - .take_while(move |token| { - if width == 0 { - return false; - } - if !token.has_zero_width() { - width -= 1; - } - true - }) - } - /// render a truncated line of tokens. - fn render(self, colors: &[Color], start: usize, end: usize, bold: bool) -> String { - assert!(start <= end); - let mut width = end - start; - let mut colored_segment = String::new(); - let mut whole_string = String::new(); - let mut color = &Color::White; - - self.truncate(start, end).for_each(|token| { - match token { - Token::Char(chr) => { - width = width.saturating_sub(1); - colored_segment.push(chr); - } - Token::Color(col) => { - add_colored_segment(&mut whole_string, &colored_segment, *color, bold); - colored_segment = String::new(); - color = colors.get(col as usize).unwrap_or(&Color::White); - } - Token::Space => { - width = width.saturating_sub(1); - colored_segment.push(' ') - } - }; - }); - - add_colored_segment(&mut whole_string, &colored_segment, *color, bold); - (0..width).for_each(|_| whole_string.push(' ')); - whole_string - } + fn is_empty(&mut self) -> bool { + for token in self { + if token.is_solid() { + return false; + } + } + true + } + fn true_length(&mut self) -> usize { + let mut last_non_space = 0; + let mut last = 0; + for token in self { + if token.has_zero_width() { + continue; + } + last += 1; + if !token.is_space() { + last_non_space = last; + } + } + last_non_space + } + fn leading_spaces(&mut self) -> usize { + self.take_while(|token| !token.is_solid()) + .filter(|token| token.is_space()) + .count() + } + fn truncate(self, mut start: usize, end: usize) -> impl 'a + Iterator { + assert!(start <= end); + let mut width = end - start; + + self.filter(move |token| { + if start > 0 && !token.has_zero_width() { + start -= 1; + return false; + } + true + }) + .take_while(move |token| { + if width == 0 { + return false; + } + if !token.has_zero_width() { + width -= 1; + } + true + }) + } + /// render a truncated line of tokens. + fn render(self, colors: &[Color], start: usize, end: usize, bold: bool) -> String { + assert!(start <= end); + let mut width = end - start; + let mut colored_segment = String::new(); + let mut whole_string = String::new(); + let mut color = &Color::White; + + self.truncate(start, end).for_each(|token| { + match token { + Token::Char(chr) => { + width = width.saturating_sub(1); + colored_segment.push(chr); + } + Token::Color(col) => { + add_colored_segment(&mut whole_string, &colored_segment, *color, bold); + colored_segment = String::new(); + color = colors.get(col as usize).unwrap_or(&Color::White); + } + Token::Space => { + width = width.saturating_sub(1); + colored_segment.push(' ') + } + }; + }); + + add_colored_segment(&mut whole_string, &colored_segment, *color, bold); + (0..width).for_each(|_| whole_string.push(' ')); + whole_string + } } // Utility functions fn succeed_when(predicate: impl FnOnce(I) -> bool) -> impl FnOnce(I) -> Option<()> { - |input| { - if predicate(input) { - Some(()) - } else { - None - } - } + |input| { + if predicate(input) { + Some(()) + } else { + None + } + } } fn add_colored_segment(base: &mut String, segment: &str, color: Color, bold: bool) { - let mut colored_segment = segment.color(color); - if bold { - colored_segment = colored_segment.bold(); - } - base.push_str(&format!("{}", colored_segment)); + let mut colored_segment = segment.color(color); + if bold { + colored_segment = colored_segment.bold(); + } + base.push_str(&format!("{}", colored_segment)); } // Basic combinators @@ -206,152 +206,152 @@ fn add_colored_segment(base: &mut String, segment: &str, color: Color, bold: boo type ParseResult<'a, R> = Option<(&'a str, R)>; fn token(s: &str, predicate: impl FnOnce(char) -> Option) -> ParseResult { - let token = s.chars().next()?; - let result = predicate(token)?; - Some((s.get(1..).unwrap(), result)) + let token = s.chars().next()?; + let result = predicate(token)?; + Some((s.get(1..).unwrap(), result)) } // Parsers /// Parses a color indiator of the format `{n}` where `n` is a digit. fn color_token(s: &str) -> ParseResult { - let (s, _) = token(s, succeed_when(|c| c == '{'))?; - let (s, color_index) = token(s, |c| c.to_digit(10))?; - let (s, _) = token(s, succeed_when(|c| c == '}'))?; - Some((s, Token::Color(color_index))) + let (s, _) = token(s, succeed_when(|c| c == '{'))?; + let (s, color_index) = token(s, |c| c.to_digit(10))?; + let (s, _) = token(s, succeed_when(|c| c == '}'))?; + Some((s, Token::Color(color_index))) } /// Parses a space. fn space_token(s: &str) -> ParseResult { - token(s, succeed_when(|c| c == ' ')).map(|(s, _)| (s, Token::Space)) + token(s, succeed_when(|c| c == ' ')).map(|(s, _)| (s, Token::Space)) } /// Parses any arbitrary character. This cannot fail. fn char_token(s: &str) -> ParseResult { - token(s, |c| Some(Token::Char(c))) + token(s, |c| Some(Token::Char(c))) } #[cfg(test)] mod test { - use super::*; - - #[test] - fn space_parses() { - assert_eq!(space_token(" "), Some(("", Token::Space))); - assert_eq!(space_token(" hello"), Some(("hello", Token::Space))); - assert_eq!(space_token(" "), Some((" ", Token::Space))); - assert_eq!(space_token(" {1}{2}"), Some(("{1}{2}", Token::Space))); - } - - #[test] - fn color_indicator_parses() { - assert_eq!(color_token("{1}"), Some(("", Token::Color(1)))); - assert_eq!(color_token("{9} "), Some((" ", Token::Color(9)))); - } - - #[test] - fn leading_spaces_counts_correctly() { - assert_eq!(Tokens("").leading_spaces(), 0); - assert_eq!(Tokens(" ").leading_spaces(), 5); - assert_eq!(Tokens(" a;lksjf;a").leading_spaces(), 5); - assert_eq!(Tokens(" {1} {5} {9} a").leading_spaces(), 6); - } - - #[test] - fn render() { - use colored::control::SHOULD_COLORIZE; - SHOULD_COLORIZE.set_override(true); - - let colors_shim = Vec::new(); - - assert_eq!( - Tokens("").render(&colors_shim, 0, 0, true), - "\u{1b}[1;37m\u{1b}[0m" - ); - - assert_eq!( - Tokens(" ").render(&colors_shim, 0, 0, true), - "\u{1b}[1;37m\u{1b}[0m" - ); - - assert_eq!( - Tokens(" ").render(&colors_shim, 0, 5, true), - "\u{1b}[1;37m \u{1b}[0m" - ); - - assert_eq!( - Tokens(" ").render(&colors_shim, 1, 5, true), - "\u{1b}[1;37m \u{1b}[0m" - ); - - assert_eq!( - Tokens(" ").render(&colors_shim, 3, 5, true), - "\u{1b}[1;37m \u{1b}[0m" - ); - - assert_eq!( - Tokens(" ").render(&colors_shim, 0, 4, true), - "\u{1b}[1;37m \u{1b}[0m" - ); - - assert_eq!( - Tokens(" ").render(&colors_shim, 0, 3, true), - "\u{1b}[1;37m \u{1b}[0m" - ); - - assert_eq!( + use super::*; + + #[test] + fn space_parses() { + assert_eq!(space_token(" "), Some(("", Token::Space))); + assert_eq!(space_token(" hello"), Some(("hello", Token::Space))); + assert_eq!(space_token(" "), Some((" ", Token::Space))); + assert_eq!(space_token(" {1}{2}"), Some(("{1}{2}", Token::Space))); + } + + #[test] + fn color_indicator_parses() { + assert_eq!(color_token("{1}"), Some(("", Token::Color(1)))); + assert_eq!(color_token("{9} "), Some((" ", Token::Color(9)))); + } + + #[test] + fn leading_spaces_counts_correctly() { + assert_eq!(Tokens("").leading_spaces(), 0); + assert_eq!(Tokens(" ").leading_spaces(), 5); + assert_eq!(Tokens(" a;lksjf;a").leading_spaces(), 5); + assert_eq!(Tokens(" {1} {5} {9} a").leading_spaces(), 6); + } + + #[test] + fn render() { + use colored::control::SHOULD_COLORIZE; + SHOULD_COLORIZE.set_override(true); + + let colors_shim = Vec::new(); + + assert_eq!( + Tokens("").render(&colors_shim, 0, 0, true), + "\u{1b}[1;37m\u{1b}[0m" + ); + + assert_eq!( + Tokens(" ").render(&colors_shim, 0, 0, true), + "\u{1b}[1;37m\u{1b}[0m" + ); + + assert_eq!( + Tokens(" ").render(&colors_shim, 0, 5, true), + "\u{1b}[1;37m \u{1b}[0m" + ); + + assert_eq!( + Tokens(" ").render(&colors_shim, 1, 5, true), + "\u{1b}[1;37m \u{1b}[0m" + ); + + assert_eq!( + Tokens(" ").render(&colors_shim, 3, 5, true), + "\u{1b}[1;37m \u{1b}[0m" + ); + + assert_eq!( + Tokens(" ").render(&colors_shim, 0, 4, true), + "\u{1b}[1;37m \u{1b}[0m" + ); + + assert_eq!( + Tokens(" ").render(&colors_shim, 0, 3, true), + "\u{1b}[1;37m \u{1b}[0m" + ); + + assert_eq!( Tokens(" {1} {5} {9} a").render(&colors_shim, 4, 10, true), "\u{1b}[1;37m\u{1b}[0m\u{1b}[1;37m\u{1b}[0m\u{1b}[1;37m \u{1b}[0m\u{1b}[1;37m a\u{1b}[0m " ); - // Tests for bold disabled - assert_eq!( - Tokens(" ").render(&colors_shim, 0, 0, false), - "\u{1b}[37m\u{1b}[0m" - ); - assert_eq!( - Tokens(" ").render(&colors_shim, 0, 5, false), - "\u{1b}[37m \u{1b}[0m" - ); - } - - #[test] - fn truncate() { - assert_eq!( - Tokens("").truncate(0, 0).collect::>(), - Tokens("").collect::>() - ); - - assert_eq!( - Tokens(" ").truncate(0, 0).collect::>(), - Tokens("").collect::>() - ); - assert_eq!( - Tokens(" ").truncate(0, 5).collect::>(), - Tokens(" ").collect::>() - ); - assert_eq!( - Tokens(" ").truncate(1, 5).collect::>(), - Tokens(" ").collect::>() - ); - assert_eq!( - Tokens(" ").truncate(3, 5).collect::>(), - Tokens(" ").collect::>() - ); - assert_eq!( - Tokens(" ").truncate(0, 4).collect::>(), - Tokens(" ").collect::>() - ); - assert_eq!( - Tokens(" ").truncate(0, 3).collect::>(), - Tokens(" ").collect::>() - ); - - assert_eq!( - Tokens(" {1} {5} {9} a") - .truncate(4, 10) - .collect::>(), - Tokens("{1}{5} {9} a").collect::>() - ); - } + // Tests for bold disabled + assert_eq!( + Tokens(" ").render(&colors_shim, 0, 0, false), + "\u{1b}[37m\u{1b}[0m" + ); + assert_eq!( + Tokens(" ").render(&colors_shim, 0, 5, false), + "\u{1b}[37m \u{1b}[0m" + ); + } + + #[test] + fn truncate() { + assert_eq!( + Tokens("").truncate(0, 0).collect::>(), + Tokens("").collect::>() + ); + + assert_eq!( + Tokens(" ").truncate(0, 0).collect::>(), + Tokens("").collect::>() + ); + assert_eq!( + Tokens(" ").truncate(0, 5).collect::>(), + Tokens(" ").collect::>() + ); + assert_eq!( + Tokens(" ").truncate(1, 5).collect::>(), + Tokens(" ").collect::>() + ); + assert_eq!( + Tokens(" ").truncate(3, 5).collect::>(), + Tokens(" ").collect::>() + ); + assert_eq!( + Tokens(" ").truncate(0, 4).collect::>(), + Tokens(" ").collect::>() + ); + assert_eq!( + Tokens(" ").truncate(0, 3).collect::>(), + Tokens(" ").collect::>() + ); + + assert_eq!( + Tokens(" {1} {5} {9} a") + .truncate(4, 10) + .collect::>(), + Tokens("{1}{5} {9} a").collect::>() + ); + } } diff --git a/src/ui/image_backends/iterm.rs b/src/ui/image_backends/iterm.rs index 4c525f47f..92611dbbb 100644 --- a/src/ui/image_backends/iterm.rs +++ b/src/ui/image_backends/iterm.rs @@ -6,58 +6,58 @@ use std::env; pub struct ITermBackend {} impl ITermBackend { - pub fn new() -> Self { - ITermBackend {} - } - - pub fn supported() -> bool { - let term_program = env::var("TERM_PROGRAM").unwrap_or_else(|_| "".to_string()); - term_program == "iTerm.app" - } + pub fn new() -> Self { + ITermBackend {} + } + + pub fn supported() -> bool { + let term_program = env::var("TERM_PROGRAM").unwrap_or_else(|_| "".to_string()); + term_program == "iTerm.app" + } } impl super::ImageBackend for ITermBackend { - fn add_image( - &self, - lines: Vec, - image: &DynamicImage, - _colors: usize, - ) -> Result { - let tty_size = unsafe { - let tty_size: winsize = std::mem::zeroed(); - ioctl(STDOUT_FILENO, TIOCGWINSZ, &tty_size); - tty_size - }; - let width_ratio = f64::from(tty_size.ws_col) / f64::from(tty_size.ws_xpixel); - let height_ratio = f64::from(tty_size.ws_row) / f64::from(tty_size.ws_ypixel); - - // resize image to fit the text height with the Lanczos3 algorithm - let image = image.resize( - u32::max_value(), - (lines.len() as f64 / height_ratio) as u32, - FilterType::Lanczos3, - ); - let _image_columns = width_ratio * f64::from(image.width()); - let image_rows = height_ratio * f64::from(image.height()); - - let mut bytes: Vec = Vec::new(); - image.write_to(&mut bytes, image::ImageOutputFormat::Png)?; - let encoded_image = base64::encode(bytes); - let mut image_data = Vec::::new(); - - image_data.extend(b"\x1B]1337;File=inline=1:"); - image_data.extend(encoded_image.bytes()); - image_data.extend(b"\x07"); - - image_data.extend(format!("\x1B[{}A", image_rows as u32 - 1).as_bytes()); // move cursor to start of image - let mut i = 0; - for line in &lines { - image_data.extend(format!("\x1B[s{}\x1B[u\x1B[1B", line).as_bytes()); - i += 1; - } - image_data - .extend(format!("\n\x1B[{}B", lines.len().max(image_rows as usize) - i).as_bytes()); // move cursor to end of image - - Ok(String::from_utf8(image_data)?) - } + fn add_image( + &self, + lines: Vec, + image: &DynamicImage, + _colors: usize, + ) -> Result { + let tty_size = unsafe { + let tty_size: winsize = std::mem::zeroed(); + ioctl(STDOUT_FILENO, TIOCGWINSZ, &tty_size); + tty_size + }; + let width_ratio = f64::from(tty_size.ws_col) / f64::from(tty_size.ws_xpixel); + let height_ratio = f64::from(tty_size.ws_row) / f64::from(tty_size.ws_ypixel); + + // resize image to fit the text height with the Lanczos3 algorithm + let image = image.resize( + u32::max_value(), + (lines.len() as f64 / height_ratio) as u32, + FilterType::Lanczos3, + ); + let _image_columns = width_ratio * f64::from(image.width()); + let image_rows = height_ratio * f64::from(image.height()); + + let mut bytes: Vec = Vec::new(); + image.write_to(&mut bytes, image::ImageOutputFormat::Png)?; + let encoded_image = base64::encode(bytes); + let mut image_data = Vec::::new(); + + image_data.extend(b"\x1B]1337;File=inline=1:"); + image_data.extend(encoded_image.bytes()); + image_data.extend(b"\x07"); + + image_data.extend(format!("\x1B[{}A", image_rows as u32 - 1).as_bytes()); // move cursor to start of image + let mut i = 0; + for line in &lines { + image_data.extend(format!("\x1B[s{}\x1B[u\x1B[1B", line).as_bytes()); + i += 1; + } + image_data + .extend(format!("\n\x1B[{}B", lines.len().max(image_rows as usize) - i).as_bytes()); // move cursor to end of image + + Ok(String::from_utf8(image_data)?) + } } diff --git a/src/ui/image_backends/kitty.rs b/src/ui/image_backends/kitty.rs index 2e87cdfa6..f5c28c8f0 100644 --- a/src/ui/image_backends/kitty.rs +++ b/src/ui/image_backends/kitty.rs @@ -1,8 +1,8 @@ use anyhow::Result; use image::{imageops::FilterType, DynamicImage, GenericImageView}; use libc::{ - c_void, ioctl, poll, pollfd, read, tcgetattr, tcsetattr, termios, winsize, ECHO, ICANON, - POLLIN, STDIN_FILENO, STDOUT_FILENO, TCSANOW, TIOCGWINSZ, + c_void, ioctl, poll, pollfd, read, tcgetattr, tcsetattr, termios, winsize, ECHO, ICANON, + POLLIN, STDIN_FILENO, STDOUT_FILENO, TCSANOW, TIOCGWINSZ, }; use std::io::{stdout, Write}; use std::time::Instant; @@ -10,133 +10,133 @@ use std::time::Instant; pub struct KittyBackend {} impl KittyBackend { - pub fn new() -> Self { - Self {} - } + pub fn new() -> Self { + Self {} + } - pub fn supported() -> bool { - // save terminal attributes and disable canonical input processing mode - let old_attributes = unsafe { - let mut old_attributes: termios = std::mem::zeroed(); - tcgetattr(STDIN_FILENO, &mut old_attributes); + pub fn supported() -> bool { + // save terminal attributes and disable canonical input processing mode + let old_attributes = unsafe { + let mut old_attributes: termios = std::mem::zeroed(); + tcgetattr(STDIN_FILENO, &mut old_attributes); - let mut new_attributes = old_attributes; - new_attributes.c_lflag &= !ICANON; - new_attributes.c_lflag &= !ECHO; - tcsetattr(STDIN_FILENO, TCSANOW, &new_attributes); - old_attributes - }; + let mut new_attributes = old_attributes; + new_attributes.c_lflag &= !ICANON; + new_attributes.c_lflag &= !ECHO; + tcsetattr(STDIN_FILENO, TCSANOW, &new_attributes); + old_attributes + }; - // generate red rgba test image - let mut test_image = Vec::::with_capacity(32 * 32 * 4); - test_image.extend( - std::iter::repeat([255, 0, 0, 255].iter()) - .take(32 * 32) - .flatten(), - ); + // generate red rgba test image + let mut test_image = Vec::::with_capacity(32 * 32 * 4); + test_image.extend( + std::iter::repeat([255, 0, 0, 255].iter()) + .take(32 * 32) + .flatten(), + ); - // print the test image with the action set to query - print!( - "\x1B_Gi=1,f=32,s=32,v=32,a=q;{}\x1B\\", - base64::encode(&test_image) - ); - stdout().flush().unwrap(); + // print the test image with the action set to query + print!( + "\x1B_Gi=1,f=32,s=32,v=32,a=q;{}\x1B\\", + base64::encode(&test_image) + ); + stdout().flush().unwrap(); - let start_time = Instant::now(); - let mut stdin_pollfd = pollfd { - fd: STDIN_FILENO, - events: POLLIN, - revents: 0, - }; - let allowed_bytes = [0x1B, b'_', b'G', b'\\']; - let mut buf = Vec::::new(); - loop { - // check for timeout while polling to avoid blocking the main thread - while unsafe { poll(&mut stdin_pollfd, 1, 0) < 1 } { - if start_time.elapsed().as_millis() > 50 { - unsafe { - tcsetattr(STDIN_FILENO, TCSANOW, &old_attributes); - } - return false; - } - } - let mut byte = 0; - unsafe { - read(STDIN_FILENO, &mut byte as *mut _ as *mut c_void, 1); - } - if allowed_bytes.contains(&byte) { - buf.push(byte); - } - if buf.starts_with(&[0x1B, b'_', b'G']) && buf.ends_with(&[0x1B, b'\\']) { - unsafe { - tcsetattr(STDIN_FILENO, TCSANOW, &old_attributes); - } - return true; - } - } - } + let start_time = Instant::now(); + let mut stdin_pollfd = pollfd { + fd: STDIN_FILENO, + events: POLLIN, + revents: 0, + }; + let allowed_bytes = [0x1B, b'_', b'G', b'\\']; + let mut buf = Vec::::new(); + loop { + // check for timeout while polling to avoid blocking the main thread + while unsafe { poll(&mut stdin_pollfd, 1, 0) < 1 } { + if start_time.elapsed().as_millis() > 50 { + unsafe { + tcsetattr(STDIN_FILENO, TCSANOW, &old_attributes); + } + return false; + } + } + let mut byte = 0; + unsafe { + read(STDIN_FILENO, &mut byte as *mut _ as *mut c_void, 1); + } + if allowed_bytes.contains(&byte) { + buf.push(byte); + } + if buf.starts_with(&[0x1B, b'_', b'G']) && buf.ends_with(&[0x1B, b'\\']) { + unsafe { + tcsetattr(STDIN_FILENO, TCSANOW, &old_attributes); + } + return true; + } + } + } } impl super::ImageBackend for KittyBackend { - fn add_image( - &self, - lines: Vec, - image: &DynamicImage, - _colors: usize, - ) -> Result { - let tty_size = unsafe { - let tty_size: winsize = std::mem::zeroed(); - ioctl(STDOUT_FILENO, TIOCGWINSZ, &tty_size); - tty_size - }; - let width_ratio = f64::from(tty_size.ws_col) / f64::from(tty_size.ws_xpixel); - let height_ratio = f64::from(tty_size.ws_row) / f64::from(tty_size.ws_ypixel); + fn add_image( + &self, + lines: Vec, + image: &DynamicImage, + _colors: usize, + ) -> Result { + let tty_size = unsafe { + let tty_size: winsize = std::mem::zeroed(); + ioctl(STDOUT_FILENO, TIOCGWINSZ, &tty_size); + tty_size + }; + let width_ratio = f64::from(tty_size.ws_col) / f64::from(tty_size.ws_xpixel); + let height_ratio = f64::from(tty_size.ws_row) / f64::from(tty_size.ws_ypixel); - // resize image to fit the text height with the Lanczos3 algorithm - let image = image.resize( - u32::max_value(), - (lines.len() as f64 / height_ratio) as u32, - FilterType::Lanczos3, - ); - let _image_columns = width_ratio * f64::from(image.width()); - let image_rows = height_ratio * f64::from(image.height()); + // resize image to fit the text height with the Lanczos3 algorithm + let image = image.resize( + u32::max_value(), + (lines.len() as f64 / height_ratio) as u32, + FilterType::Lanczos3, + ); + let _image_columns = width_ratio * f64::from(image.width()); + let image_rows = height_ratio * f64::from(image.height()); - // convert the image to rgba samples - let rgba_image = image.to_rgba8(); - let flat_samples = rgba_image.as_flat_samples(); - let raw_image = flat_samples - .image_slice() - .expect("Conversion from image to rgba samples failed"); - assert_eq!( - image.width() as usize * image.height() as usize * 4, - raw_image.len() - ); + // convert the image to rgba samples + let rgba_image = image.to_rgba8(); + let flat_samples = rgba_image.as_flat_samples(); + let raw_image = flat_samples + .image_slice() + .expect("Conversion from image to rgba samples failed"); + assert_eq!( + image.width() as usize * image.height() as usize * 4, + raw_image.len() + ); - let encoded_image = base64::encode(&raw_image); // image data is base64 encoded - let mut image_data = Vec::::new(); - for chunk in encoded_image.as_bytes().chunks(4096) { - // send a 4096 byte chunk of base64 encoded rgba image data - image_data.extend( - format!( - "\x1B_Gf=32,s={},v={},m=1,a=T;", - image.width(), - image.height() - ) - .as_bytes(), - ); - image_data.extend(chunk); - image_data.extend(b"\x1B\\"); - } - image_data.extend(b"\x1B_Gm=0;\x1B\\"); // write empty last chunk - image_data.extend(format!("\x1B[{}A", image_rows as u32 - 1).as_bytes()); // move cursor to start of image - let mut i = 0; - for line in &lines { - image_data.extend(format!("\x1B[s{}\x1B[u\x1B[1B", line).as_bytes()); - i += 1; - } - image_data - .extend(format!("\n\x1B[{}B", lines.len().max(image_rows as usize) - i).as_bytes()); // move cursor to end of image + let encoded_image = base64::encode(&raw_image); // image data is base64 encoded + let mut image_data = Vec::::new(); + for chunk in encoded_image.as_bytes().chunks(4096) { + // send a 4096 byte chunk of base64 encoded rgba image data + image_data.extend( + format!( + "\x1B_Gf=32,s={},v={},m=1,a=T;", + image.width(), + image.height() + ) + .as_bytes(), + ); + image_data.extend(chunk); + image_data.extend(b"\x1B\\"); + } + image_data.extend(b"\x1B_Gm=0;\x1B\\"); // write empty last chunk + image_data.extend(format!("\x1B[{}A", image_rows as u32 - 1).as_bytes()); // move cursor to start of image + let mut i = 0; + for line in &lines { + image_data.extend(format!("\x1B[s{}\x1B[u\x1B[1B", line).as_bytes()); + i += 1; + } + image_data + .extend(format!("\n\x1B[{}B", lines.len().max(image_rows as usize) - i).as_bytes()); // move cursor to end of image - Ok(String::from_utf8(image_data)?) - } + Ok(String::from_utf8(image_data)?) + } } diff --git a/src/ui/image_backends/mod.rs b/src/ui/image_backends/mod.rs index 1e34e1382..c791de5fb 100644 --- a/src/ui/image_backends/mod.rs +++ b/src/ui/image_backends/mod.rs @@ -9,35 +9,35 @@ pub mod kitty; pub mod sixel; pub trait ImageBackend { - fn add_image(&self, lines: Vec, image: &DynamicImage, colors: usize) -> Result; + fn add_image(&self, lines: Vec, image: &DynamicImage, colors: usize) -> Result; } pub fn get_best_backend() -> Option> { - #[cfg(not(windows))] - if kitty::KittyBackend::supported() { - Some(Box::new(kitty::KittyBackend::new())) - } else if iterm::ITermBackend::supported() { - Some(Box::new(iterm::ITermBackend::new())) - } else if sixel::SixelBackend::supported() { - Some(Box::new(sixel::SixelBackend::new())) - } else { - None - } + #[cfg(not(windows))] + if kitty::KittyBackend::supported() { + Some(Box::new(kitty::KittyBackend::new())) + } else if iterm::ITermBackend::supported() { + Some(Box::new(iterm::ITermBackend::new())) + } else if sixel::SixelBackend::supported() { + Some(Box::new(sixel::SixelBackend::new())) + } else { + None + } - #[cfg(windows)] - None + #[cfg(windows)] + None } pub fn get_image_backend(backend_name: &str) -> Option> { - #[cfg(not(windows))] - let backend = Some(match backend_name { - "kitty" => Box::new(kitty::KittyBackend::new()) as Box, - "iterm" => Box::new(iterm::ITermBackend::new()) as Box, - "sixel" => Box::new(sixel::SixelBackend::new()) as Box, - _ => unreachable!(), - }); + #[cfg(not(windows))] + let backend = Some(match backend_name { + "kitty" => Box::new(kitty::KittyBackend::new()) as Box, + "iterm" => Box::new(iterm::ITermBackend::new()) as Box, + "sixel" => Box::new(sixel::SixelBackend::new()) as Box, + _ => unreachable!(), + }); - #[cfg(windows)] - let backend = None; - backend + #[cfg(windows)] + let backend = None; + backend } diff --git a/src/ui/image_backends/sixel.rs b/src/ui/image_backends/sixel.rs index b3308efde..2e66dc94c 100644 --- a/src/ui/image_backends/sixel.rs +++ b/src/ui/image_backends/sixel.rs @@ -1,12 +1,12 @@ use anyhow::{Context, Result}; use color_quant::NeuQuant; use image::{ - imageops::{colorops, FilterType}, - DynamicImage, GenericImageView, ImageBuffer, Pixel, Rgb, + imageops::{colorops, FilterType}, + DynamicImage, GenericImageView, ImageBuffer, Pixel, Rgb, }; use libc::{ - c_void, ioctl, poll, pollfd, read, tcgetattr, tcsetattr, termios, winsize, ECHO, ICANON, - POLLIN, STDIN_FILENO, STDOUT_FILENO, TCSANOW, TIOCGWINSZ, + c_void, ioctl, poll, pollfd, read, tcgetattr, tcsetattr, termios, winsize, ECHO, ICANON, + POLLIN, STDIN_FILENO, STDOUT_FILENO, TCSANOW, TIOCGWINSZ, }; use std::io::{stdout, Write}; use std::time::Instant; @@ -14,159 +14,159 @@ use std::time::Instant; pub struct SixelBackend {} impl SixelBackend { - pub fn new() -> Self { - Self {} - } + pub fn new() -> Self { + Self {} + } - pub fn supported() -> bool { - // save terminal attributes and disable canonical input processing mode - let old_attributes = unsafe { - let mut old_attributes: termios = std::mem::zeroed(); - tcgetattr(STDIN_FILENO, &mut old_attributes); + pub fn supported() -> bool { + // save terminal attributes and disable canonical input processing mode + let old_attributes = unsafe { + let mut old_attributes: termios = std::mem::zeroed(); + tcgetattr(STDIN_FILENO, &mut old_attributes); - let mut new_attributes = old_attributes; - new_attributes.c_lflag &= !ICANON; - new_attributes.c_lflag &= !ECHO; - tcsetattr(STDIN_FILENO, TCSANOW, &new_attributes); - old_attributes - }; + let mut new_attributes = old_attributes; + new_attributes.c_lflag &= !ICANON; + new_attributes.c_lflag &= !ECHO; + tcsetattr(STDIN_FILENO, TCSANOW, &new_attributes); + old_attributes + }; - // ask for the primary device attribute string - print!("\x1B[c"); - stdout().flush().unwrap(); + // ask for the primary device attribute string + print!("\x1B[c"); + stdout().flush().unwrap(); - let start_time = Instant::now(); - let mut stdin_pollfd = pollfd { - fd: STDIN_FILENO, - events: POLLIN, - revents: 0, - }; - let mut buf = Vec::::new(); - loop { - // check for timeout while polling to avoid blocking the main thread - while unsafe { poll(&mut stdin_pollfd, 1, 0) < 1 } { - if start_time.elapsed().as_millis() > 50 { - unsafe { - tcsetattr(STDIN_FILENO, TCSANOW, &old_attributes); - } - return false; - } - } - let mut byte = 0; - unsafe { - read(STDIN_FILENO, &mut byte as *mut _ as *mut c_void, 1); - } - buf.push(byte); - if buf.starts_with(&[0x1B, b'[', b'?']) && buf.ends_with(&[b'c']) { - for attribute in buf[3..(buf.len() - 1)].split(|x| *x == b';') { - if attribute == [b'4'] { - unsafe { - tcsetattr(STDIN_FILENO, TCSANOW, &old_attributes); - } - return true; - } - } - } - } - } + let start_time = Instant::now(); + let mut stdin_pollfd = pollfd { + fd: STDIN_FILENO, + events: POLLIN, + revents: 0, + }; + let mut buf = Vec::::new(); + loop { + // check for timeout while polling to avoid blocking the main thread + while unsafe { poll(&mut stdin_pollfd, 1, 0) < 1 } { + if start_time.elapsed().as_millis() > 50 { + unsafe { + tcsetattr(STDIN_FILENO, TCSANOW, &old_attributes); + } + return false; + } + } + let mut byte = 0; + unsafe { + read(STDIN_FILENO, &mut byte as *mut _ as *mut c_void, 1); + } + buf.push(byte); + if buf.starts_with(&[0x1B, b'[', b'?']) && buf.ends_with(&[b'c']) { + for attribute in buf[3..(buf.len() - 1)].split(|x| *x == b';') { + if attribute == [b'4'] { + unsafe { + tcsetattr(STDIN_FILENO, TCSANOW, &old_attributes); + } + return true; + } + } + } + } + } } impl super::ImageBackend for SixelBackend { - #[allow(clippy::map_entry)] - fn add_image(&self, lines: Vec, image: &DynamicImage, colors: usize) -> Result { - let tty_size = unsafe { - let tty_size: winsize = std::mem::zeroed(); - ioctl(STDOUT_FILENO, TIOCGWINSZ, &tty_size); - tty_size - }; - let cw = tty_size.ws_xpixel / tty_size.ws_col; - let lh = tty_size.ws_ypixel / tty_size.ws_row; - let width_ratio = 1.0 / cw as f64; - let height_ratio = 1.0 / lh as f64; + #[allow(clippy::map_entry)] + fn add_image(&self, lines: Vec, image: &DynamicImage, colors: usize) -> Result { + let tty_size = unsafe { + let tty_size: winsize = std::mem::zeroed(); + ioctl(STDOUT_FILENO, TIOCGWINSZ, &tty_size); + tty_size + }; + let cw = tty_size.ws_xpixel / tty_size.ws_col; + let lh = tty_size.ws_ypixel / tty_size.ws_row; + let width_ratio = 1.0 / cw as f64; + let height_ratio = 1.0 / lh as f64; - // resize image to fit the text height with the Lanczos3 algorithm - let image = image.resize( - u32::max_value(), - (lines.len() as f64 / height_ratio) as u32, - FilterType::Lanczos3, - ); - let image_columns = width_ratio * image.width() as f64; - let image_rows = height_ratio * image.height() as f64; + // resize image to fit the text height with the Lanczos3 algorithm + let image = image.resize( + u32::max_value(), + (lines.len() as f64 / height_ratio) as u32, + FilterType::Lanczos3, + ); + let image_columns = width_ratio * image.width() as f64; + let image_rows = height_ratio * image.height() as f64; - let rgba_image = image.to_rgba8(); // convert the image to rgba samples - let flat_samples = rgba_image.as_flat_samples(); - let mut rgba_image = rgba_image.clone(); - // reduce the amount of colors using dithering - let pixels = flat_samples - .image_slice() - .with_context(|| "Error while slicing the image")?; - colorops::dither(&mut rgba_image, &NeuQuant::new(10, colors, pixels)); + let rgba_image = image.to_rgba8(); // convert the image to rgba samples + let flat_samples = rgba_image.as_flat_samples(); + let mut rgba_image = rgba_image.clone(); + // reduce the amount of colors using dithering + let pixels = flat_samples + .image_slice() + .with_context(|| "Error while slicing the image")?; + colorops::dither(&mut rgba_image, &NeuQuant::new(10, colors, pixels)); - let rgb_image = ImageBuffer::from_fn(rgba_image.width(), rgba_image.height(), |x, y| { - let rgba_pixel = rgba_image.get_pixel(x, y); - let mut rgb_pixel = rgba_pixel.to_rgb(); - for subpixel in &mut rgb_pixel.0 { - *subpixel = (*subpixel as f32 / 255.0 * rgba_pixel[3] as f32) as u8; - } - rgb_pixel - }); + let rgb_image = ImageBuffer::from_fn(rgba_image.width(), rgba_image.height(), |x, y| { + let rgba_pixel = rgba_image.get_pixel(x, y); + let mut rgb_pixel = rgba_pixel.to_rgb(); + for subpixel in &mut rgb_pixel.0 { + *subpixel = (*subpixel as f32 / 255.0 * rgba_pixel[3] as f32) as u8; + } + rgb_pixel + }); - let mut image_data = Vec::::new(); - image_data.extend(b"\x1BPq"); // start sixel data - image_data.extend(format!("\"1;1;{};{}", image.width(), image.height()).as_bytes()); + let mut image_data = Vec::::new(); + image_data.extend(b"\x1BPq"); // start sixel data + image_data.extend(format!("\"1;1;{};{}", image.width(), image.height()).as_bytes()); - let mut colors = std::collections::HashMap::, u8>::new(); - // subtract 1 -> divide -> add 1 to round up the integer division - for i in 0..((rgb_image.height() - 1) / 6 + 1) { - let sixel_row = rgb_image.view( - 0, - i * 6, - rgb_image.width(), - std::cmp::min(6, rgb_image.height() - i * 6), - ); - for (_, _, pixel) in sixel_row.pixels() { - if !colors.contains_key(&pixel) { - // sixel uses percentages for rgb values - let color_multiplier = 100.0 / 255.0; - image_data.extend( - format!( - "#{};2;{};{};{}", - colors.len(), - (pixel[0] as f32 * color_multiplier) as u32, - (pixel[1] as f32 * color_multiplier) as u32, - (pixel[2] as f32 * color_multiplier) as u32 - ) - .as_bytes(), - ); - colors.insert(pixel, colors.len() as u8); - } - } - for (color, color_index) in &colors { - let mut sixel_samples = vec![0; sixel_row.width() as usize]; - sixel_samples.resize(sixel_row.width() as usize, 0); - for (x, y, pixel) in sixel_row.pixels() { - if color == &pixel { - sixel_samples[x as usize] |= 1 << y; - } - } - image_data.extend(format!("#{}", color_index).bytes()); - image_data.extend(sixel_samples.iter().map(|x| x + 0x3F)); - image_data.push(b'$'); - } - image_data.push(b'-'); - } - image_data.extend(b"\x1B\\"); + let mut colors = std::collections::HashMap::, u8>::new(); + // subtract 1 -> divide -> add 1 to round up the integer division + for i in 0..((rgb_image.height() - 1) / 6 + 1) { + let sixel_row = rgb_image.view( + 0, + i * 6, + rgb_image.width(), + std::cmp::min(6, rgb_image.height() - i * 6), + ); + for (_, _, pixel) in sixel_row.pixels() { + if !colors.contains_key(&pixel) { + // sixel uses percentages for rgb values + let color_multiplier = 100.0 / 255.0; + image_data.extend( + format!( + "#{};2;{};{};{}", + colors.len(), + (pixel[0] as f32 * color_multiplier) as u32, + (pixel[1] as f32 * color_multiplier) as u32, + (pixel[2] as f32 * color_multiplier) as u32 + ) + .as_bytes(), + ); + colors.insert(pixel, colors.len() as u8); + } + } + for (color, color_index) in &colors { + let mut sixel_samples = vec![0; sixel_row.width() as usize]; + sixel_samples.resize(sixel_row.width() as usize, 0); + for (x, y, pixel) in sixel_row.pixels() { + if color == &pixel { + sixel_samples[x as usize] |= 1 << y; + } + } + image_data.extend(format!("#{}", color_index).bytes()); + image_data.extend(sixel_samples.iter().map(|x| x + 0x3F)); + image_data.push(b'$'); + } + image_data.push(b'-'); + } + image_data.extend(b"\x1B\\"); - image_data.extend(format!("\x1B[{}A", image_rows as u32).as_bytes()); // move cursor to top-left corner - image_data.extend(format!("\x1B[{}C", image_columns as u32 + 1).as_bytes()); // move cursor to top-right corner of image - let mut i = 0; - for line in &lines { - image_data.extend(format!("\x1B[s{}\x1B[u\x1B[1B", line).as_bytes()); - i += 1; - } - image_data - .extend(format!("\n\x1B[{}B", lines.len().max(image_rows as usize) - i).as_bytes()); // move cursor to end of image + image_data.extend(format!("\x1B[{}A", image_rows as u32).as_bytes()); // move cursor to top-left corner + image_data.extend(format!("\x1B[{}C", image_columns as u32 + 1).as_bytes()); // move cursor to top-right corner of image + let mut i = 0; + for line in &lines { + image_data.extend(format!("\x1B[s{}\x1B[u\x1B[1B", line).as_bytes()); + i += 1; + } + image_data + .extend(format!("\n\x1B[{}B", lines.len().max(image_rows as usize) - i).as_bytes()); // move cursor to end of image - Ok(String::from_utf8(image_data)?) - } + Ok(String::from_utf8(image_data)?) + } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index b8e401b38..0b3446eb0 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -7,53 +7,53 @@ pub mod printer; pub mod text_color; pub fn get_ascii_colors( - ascii_language: &Option, - dominant_language: &Language, - ascii_colors: &[String], - true_color: bool, + ascii_language: &Option, + dominant_language: &Language, + ascii_colors: &[String], + true_color: bool, ) -> Vec { - let language = if let Some(ascii_language) = ascii_language { - ascii_language - } else { - &dominant_language - }; + let language = if let Some(ascii_language) = ascii_language { + ascii_language + } else { + &dominant_language + }; - let colors = language.get_colors(true_color); + let colors = language.get_colors(true_color); - let colors: Vec = colors - .iter() - .enumerate() - .map(|(index, default_color)| { - if let Some(color_num) = ascii_colors.get(index) { - if let Some(color) = num_to_color(color_num) { - return color; - } - } - *default_color - }) - .collect(); - colors + let colors: Vec = colors + .iter() + .enumerate() + .map(|(index, default_color)| { + if let Some(color_num) = ascii_colors.get(index) { + if let Some(color) = num_to_color(color_num) { + return color; + } + } + *default_color + }) + .collect(); + colors } fn num_to_color(num: &str) -> Option { - let color = match num { - "0" => Color::Black, - "1" => Color::Red, - "2" => Color::Green, - "3" => Color::Yellow, - "4" => Color::Blue, - "5" => Color::Magenta, - "6" => Color::Cyan, - "7" => Color::White, - "8" => Color::BrightBlack, - "9" => Color::BrightRed, - "10" => Color::BrightGreen, - "11" => Color::BrightYellow, - "12" => Color::BrightBlue, - "13" => Color::BrightMagenta, - "14" => Color::BrightCyan, - "15" => Color::BrightWhite, - _ => return None, - }; - Some(color) + let color = match num { + "0" => Color::Black, + "1" => Color::Red, + "2" => Color::Green, + "3" => Color::Yellow, + "4" => Color::Blue, + "5" => Color::Magenta, + "6" => Color::Cyan, + "7" => Color::White, + "8" => Color::BrightBlack, + "9" => Color::BrightRed, + "10" => Color::BrightGreen, + "11" => Color::BrightYellow, + "12" => Color::BrightBlue, + "13" => Color::BrightMagenta, + "14" => Color::BrightCyan, + "15" => Color::BrightWhite, + _ => return None, + }; + Some(color) } diff --git a/src/ui/printer.rs b/src/ui/printer.rs index 111fbfd45..4af4d637d 100644 --- a/src/ui/printer.rs +++ b/src/ui/printer.rs @@ -10,100 +10,100 @@ const CENTER_PAD_LENGTH: usize = 3; #[derive(EnumString, EnumIter, IntoStaticStr)] #[strum(serialize_all = "lowercase")] pub enum SerializationFormat { - Json, - Yaml, + Json, + Yaml, } pub struct Printer { - writer: W, - info: Info, + writer: W, + info: Info, } impl Printer { - pub fn new(writer: W, info: Info) -> Self { - Self { writer, info } - } + pub fn new(writer: W, info: Info) -> Self { + Self { writer, info } + } - pub fn print(&mut self) -> Result<()> { - match &self.info.config.output { - Some(format) => match format { - SerializationFormat::Json => { - writeln!(self.writer, "{}", serde_json::to_string_pretty(&self.info)?)? - } - SerializationFormat::Yaml => { - writeln!(self.writer, "{}", serde_yaml::to_string(&self.info)?)? - } - }, - 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(); + pub fn print(&mut self) -> Result<()> { + match &self.info.config.output { + Some(format) => match format { + SerializationFormat::Json => { + writeln!(self.writer, "{}", serde_json::to_string_pretty(&self.info)?)? + } + SerializationFormat::Yaml => { + writeln!(self.writer, "{}", serde_yaml::to_string(&self.info)?)? + } + }, + 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 { - let image_backend = self - .info - .config - .image_backend - .as_ref() - .with_context(|| "Could not detect a supported image backend")?; + if self.info.config.art_off { + buf.push_str(&info_str); + } else if let Some(custom_image) = &self.info.config.image { + let image_backend = self + .info + .config + .image_backend + .as_ref() + .with_context(|| "Could not detect a supported image backend")?; - buf.push_str( - &image_backend - .add_image( - info_lines.map(|s| format!("{}{}", center_pad, s)).collect(), - custom_image, - self.info.config.image_color_resolution, - ) - .with_context(|| "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, - ) - }; + buf.push_str( + &image_backend + .add_image( + info_lines.map(|s| format!("{}{}", center_pad, s)).collect(), + custom_image, + self.info.config.image_color_resolution, + ) + .with_context(|| "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)?; - } - } - Ok(()) - } + write!(self.writer, "{}", buf)?; + } + } + Ok(()) + } - fn get_ascii(&self) -> &str { - let language = if let Some(ascii_language) = &self.info.config.ascii_language { - ascii_language - } else { - &self.info.dominant_language - }; + fn get_ascii(&self) -> &str { + let language = if let Some(ascii_language) = &self.info.config.ascii_language { + ascii_language + } else { + &self.info.dominant_language + }; - language.get_ascii_art() - } + language.get_ascii_art() + } } diff --git a/src/ui/text_color.rs b/src/ui/text_color.rs index 450a39287..15ea3d819 100644 --- a/src/ui/text_color.rs +++ b/src/ui/text_color.rs @@ -2,48 +2,48 @@ use crate::ui::num_to_color; use colored::Color; pub struct TextColor { - pub title: Color, - pub tilde: Color, - pub underline: Color, - pub subtitle: Color, - pub colon: Color, - pub info: Color, + pub title: Color, + pub tilde: Color, + pub underline: Color, + pub subtitle: Color, + pub colon: Color, + pub info: Color, } impl TextColor { - fn new(color: Color) -> TextColor { - TextColor { - title: color, - tilde: Color::White, - underline: Color::White, - subtitle: color, - colon: Color::White, - info: Color::White, - } - } + fn new(color: Color) -> TextColor { + TextColor { + title: color, + tilde: Color::White, + underline: Color::White, + subtitle: color, + colon: Color::White, + info: Color::White, + } + } - pub fn get_text_colors(text_colors: &[String], default_colors: &[Color]) -> TextColor { - let mut text_color_set = TextColor::new(default_colors[0]); - if !text_colors.is_empty() { - let custom_color = text_colors - .iter() - .map(|color_num| { - let custom = num_to_color(color_num); - match custom { - Some(custom) => custom, - None => Color::White, - } - }) - .collect::>(); + pub fn get_text_colors(text_colors: &[String], default_colors: &[Color]) -> TextColor { + let mut text_color_set = TextColor::new(default_colors[0]); + if !text_colors.is_empty() { + let custom_color = text_colors + .iter() + .map(|color_num| { + let custom = num_to_color(color_num); + match custom { + Some(custom) => custom, + None => Color::White, + } + }) + .collect::>(); - text_color_set.title = *custom_color.get(0).unwrap_or(&default_colors[0]); - text_color_set.tilde = *custom_color.get(1).unwrap_or(&Color::White); - text_color_set.underline = *custom_color.get(2).unwrap_or(&Color::White); - text_color_set.subtitle = *custom_color.get(3).unwrap_or(&default_colors[0]); - text_color_set.colon = *custom_color.get(4).unwrap_or(&Color::White); - text_color_set.info = *custom_color.get(5).unwrap_or(&Color::White); - } + text_color_set.title = *custom_color.get(0).unwrap_or(&default_colors[0]); + text_color_set.tilde = *custom_color.get(1).unwrap_or(&Color::White); + text_color_set.underline = *custom_color.get(2).unwrap_or(&Color::White); + text_color_set.subtitle = *custom_color.get(3).unwrap_or(&default_colors[0]); + text_color_set.colon = *custom_color.get(4).unwrap_or(&Color::White); + text_color_set.info = *custom_color.get(5).unwrap_or(&Color::White); + } - text_color_set - } + text_color_set + } }