Skip to content

Support repos "without source code" #1580

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 6 additions & 18 deletions src/info/langs/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use anyhow::{Context, Result};
use language::{Language, LanguageType};
use std::collections::HashMap;
use std::path::Path;
use strum::IntoEnumIterator;

pub mod language;

pub fn get_main_language(loc_by_language: &[(Language, usize)]) -> Language {
loc_by_language[0].0
pub fn get_main_language(loc_by_language_opt: Option<&Vec<(Language, usize)>>) -> Option<Language> {
loc_by_language_opt.map(|loc_by_language| loc_by_language[0].0)
}

/// Returns a vector of tuples containing all the languages detected inside the repository.
Expand All @@ -18,21 +17,10 @@ pub fn get_loc_by_language_sorted(
globs_to_exclude: &[String],
language_types: &[LanguageType],
include_hidden: bool,
) -> Result<Vec<(Language, usize)>> {
) -> Option<Vec<(Language, usize)>> {
let locs = get_locs(dir, globs_to_exclude, language_types, include_hidden);

let loc_by_language = get_loc_by_language(&locs).with_context(|| {
format!(
"No source code found in the repository at '{}'.\n\
Note: Some language types (prose, data) are excluded by default. \
Consider using the '--type' option to include them.",
dir.display()
)
})?;

let loc_by_language_sorted = sort_by_loc(loc_by_language);

Ok(loc_by_language_sorted)
let loc_by_language_opt = get_loc_by_language(&locs);
loc_by_language_opt.map(sort_by_loc)
}

fn sort_by_loc(map: HashMap<Language, usize>) -> Vec<(Language, usize)> {
Expand All @@ -44,7 +32,7 @@ fn sort_by_loc(map: HashMap<Language, usize>) -> Vec<(Language, usize)> {
fn get_loc_by_language(languages: &tokei::Languages) -> Option<HashMap<Language, usize>> {
let mut loc_by_language = HashMap::new();

for (language_name, language) in languages.iter() {
for (language_name, language) in languages {
let loc = language::loc(language_name, language);

if loc == 0 {
Expand Down
44 changes: 24 additions & 20 deletions src/info/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ mod url;
pub mod utils;
mod version;

#[derive(Serialize)]
#[derive(Serialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct Info {
title: Option<Title>,
Expand All @@ -65,7 +65,7 @@ pub struct Info {
#[serde(skip_serializing)]
no_bold: bool,
#[serde(skip_serializing)]
pub dominant_language: Language,
pub dominant_language: Option<Language>,
#[serde(skip_serializing)]
pub ascii_colors: Vec<DynColors>,
}
Expand Down Expand Up @@ -158,11 +158,11 @@ pub fn build_info(cli_options: &CliOptions) -> Result<Info> {
let loc_by_language = loc_by_language_sorted_handle
.join()
.ok()
.context("BUG: panic in language statistics thread")??;
let dominant_language = langs::get_main_language(&loc_by_language);
.context("BUG: panic in language statistics thread")?;
let dominant_language = langs::get_main_language(loc_by_language.as_ref());
let ascii_colors = get_ascii_colors(
dominant_language.as_ref(),
cli_options.ascii.ascii_language.as_ref(),
&dominant_language,
&cli_options.ascii.ascii_colors,
true_color,
);
Expand All @@ -185,7 +185,7 @@ pub fn build_info(cli_options: &CliOptions) -> Result<Info> {
.version(&repo, manifest.as_ref())?
.created(&git_metrics, iso_time)
.languages(
&loc_by_language,
loc_by_language.as_ref(),
true_color,
number_of_languages_to_display,
&text_colors,
Expand All @@ -208,7 +208,7 @@ pub fn build_info(cli_options: &CliOptions) -> Result<Info> {
globs_to_exclude,
number_separator,
)?
.loc(&loc_by_language, number_separator)
.loc(loc_by_language.as_ref(), number_separator)
.size(&repo, number_separator)
.license(&repo_path, manifest.as_ref())?
.build(cli_options, text_colors, dominant_language, ascii_colors))
Expand Down Expand Up @@ -318,21 +318,23 @@ impl InfoBuilder {

fn languages(
mut self,
loc_by_language: &[(Language, usize)],
loc_by_language_opt: Option<&Vec<(Language, usize)>>,
true_color: bool,
number_of_languages: usize,
text_colors: &TextColors,
cli_options: &CliOptions,
) -> Self {
if !self.disabled_fields.contains(&InfoType::Languages) {
let languages = LanguagesInfo::new(
loc_by_language,
true_color,
number_of_languages,
text_colors.info,
cli_options.visuals.nerd_fonts,
);
self.info_fields.push(Box::new(languages));
if let Some(loc_by_language) = loc_by_language_opt {
let languages = LanguagesInfo::new(
loc_by_language,
true_color,
number_of_languages,
text_colors.info,
cli_options.visuals.nerd_fonts,
);
self.info_fields.push(Box::new(languages));
}
}
self
}
Expand Down Expand Up @@ -429,12 +431,14 @@ impl InfoBuilder {

fn loc(
mut self,
loc_by_language: &[(Language, usize)],
loc_by_language_opt: Option<&Vec<(Language, usize)>>,
number_separator: NumberSeparator,
) -> Self {
if !self.disabled_fields.contains(&InfoType::LinesOfCode) {
let lines_of_code = LocInfo::new(loc_by_language, number_separator);
self.info_fields.push(Box::new(lines_of_code));
if let Some(loc_by_language) = loc_by_language_opt {
let lines_of_code = LocInfo::new(loc_by_language, number_separator);
self.info_fields.push(Box::new(lines_of_code));
}
}
self
}
Expand All @@ -443,7 +447,7 @@ impl InfoBuilder {
self,
cli_options: &CliOptions,
text_colors: TextColors,
dominant_language: Language,
dominant_language: Option<Language>,
ascii_colors: Vec<DynColors>,
) -> Info {
Info {
Expand Down
8 changes: 5 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use clap::{CommandFactory, Parser};
use human_panic::setup_panic;
use onefetch::cli::{self, CliOptions};
use onefetch::info::build_info;
use onefetch::ui::printer::Printer;
use onefetch::ui::printer::factory::PrinterFactory;
use std::io;

fn main() -> Result<()> {
Expand All @@ -32,9 +32,11 @@ fn main() -> Result<()> {

let info = build_info(&cli_options)?;

let mut printer = Printer::new(io::BufWriter::new(io::stdout()), info, cli_options)?;
let printer = PrinterFactory::new(info, cli_options)?.create()?;

printer.print()?;
let mut writer = io::BufWriter::new(io::stdout());

printer.print(&mut writer)?;

Ok(())
}
64 changes: 42 additions & 22 deletions src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,26 @@ pub mod printer;
pub mod text_colors;

pub fn get_ascii_colors(
ascii_language: Option<&Language>,
dominant_language: &Language,
language_opt: Option<&Language>,
override_language_opt: Option<&Language>,
ascii_colors: &[u8],
true_color: bool,
) -> Vec<DynColors> {
let language = if let Some(ascii_language) = ascii_language {
ascii_language
} else {
dominant_language
let language_colors = match language_opt {
Some(lang) => override_language_opt.unwrap_or(lang).get_colors(true_color),
None => vec![DynColors::Ansi(AnsiColors::White)],
};
if ascii_colors.is_empty() {
return language_colors;
}

let mut language_colors: Vec<DynColors> = language.get_colors(true_color);
let mut colors: Vec<DynColors> = ascii_colors.iter().map(num_to_color).collect();

if ascii_colors.is_empty() {
language_colors
} else {
let mut colors: Vec<DynColors> = ascii_colors.iter().map(num_to_color).collect();

if language_colors.len() > colors.len() {
let mut missing = language_colors.drain(colors.len()..).collect();
colors.append(&mut missing);
}
colors
if language_colors.len() > colors.len() {
colors.extend(language_colors.into_iter().skip(colors.len()));
}

colors
}

pub fn num_to_color(num: &u8) -> DynColors {
Expand Down Expand Up @@ -63,9 +59,33 @@ mod test {
assert_eq!(num_to_color(&u8::MAX), DynColors::Ansi(AnsiColors::Default));
}

#[test]
fn get_ascii_colors_no_language_no_custom_language_custom_colors() {
let colors = get_ascii_colors(None, None, &[3, 5, 8], false);
assert_eq!(colors.len(), 3);
assert_eq!(
colors,
vec![num_to_color(&3), num_to_color(&5), num_to_color(&8)]
);
}

#[test]
fn get_ascii_colors_no_language_no_custom_language() {
let colors = get_ascii_colors(None, None, &[], false);
assert_eq!(colors.len(), 1);
assert_eq!(colors, vec![DynColors::Ansi(AnsiColors::White)]);
}

#[test]
fn get_ascii_colors_no_language_with_custom_language() {
let colors = get_ascii_colors(None, Some(&Language::Python), &[], false);
assert_eq!(colors.len(), 1);
assert_eq!(colors, vec![DynColors::Ansi(AnsiColors::White)]);
}

#[test]
fn get_ascii_colors_no_custom_language_no_custom_colors_no_true_color() {
let colors = get_ascii_colors(None.as_ref(), &Language::Rust, &[], false);
let colors = get_ascii_colors(Some(&Language::Rust), None, &[], false);
assert_eq!(colors.len(), 2);
assert_eq!(
colors,
Expand All @@ -78,7 +98,7 @@ mod test {

#[test]
fn get_ascii_colors_no_custom_language_no_custom_colors_true_color() {
let colors = get_ascii_colors(None, &Language::Rust, &[], true);
let colors = get_ascii_colors(Some(&Language::Rust), None, &[], true);
assert_eq!(colors.len(), 2);
assert_eq!(
colors,
Expand All @@ -88,14 +108,14 @@ mod test {

#[test]
fn get_ascii_colors_custom_language_no_custom_colors_no_true_color() {
let colors = get_ascii_colors(Some(&Language::Sh), &Language::Rust, &[], false);
let colors = get_ascii_colors(Some(&Language::Rust), Some(&Language::Sh), &[], false);
assert_eq!(colors.len(), 1);
assert_eq!(colors, vec![DynColors::Ansi(AnsiColors::Green)]);
}

#[test]
fn get_ascii_colors_no_custom_language_custom_colors_no_true_color() {
let colors = get_ascii_colors(None.as_ref(), &Language::Rust, &[2, 3], false);
let colors = get_ascii_colors(Some(&Language::Rust), None, &[2, 3], false);
assert_eq!(colors.len(), 2);
assert_eq!(colors, vec![num_to_color(&2), num_to_color(&3)]);
}
Expand All @@ -104,7 +124,7 @@ mod test {
fn get_ascii_colors_fill_custom_colors_with_language_colors() {
// When custom ascii colors are not enough for the given language,
// language colors should be used as default
let colors = get_ascii_colors(None, &Language::Go, &[0], false);
let colors = get_ascii_colors(Some(&Language::Go), None, &[0], false);
assert_eq!(colors.len(), 3);
assert_eq!(
colors,
Expand Down
Loading