Skip to content

Commit

Permalink
add langs module
Browse files Browse the repository at this point in the history
  • Loading branch information
o2sh committed Oct 18, 2021
1 parent be7787e commit 8699d26
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 105 deletions.
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@

### Adding support for a new language

Adding support for a new Language consists in adding a new entry to the `define_language!` macro in [language.rs](src/info/language.rs) and filling it in with the right data.
Adding support for a new Language consists in adding a new entry to the `define_language!` macro in [language.rs](src/info/langs/language.rs) and filling it in with the right data.

**Example**:

` { CSharp, "csharp.ascii", define_colors!( [Color::Blue, Color::Magenta] ), "c#" }, `
`{ CSharp, "csharp.ascii", define_colors!( [Color::Blue, Color::Magenta] ), "c#" },`

The first item `CSharp` corresponds to the name of the language as defined in [tokei](https://github.com/XAMPPRocky/tokei). The second item `csharp.ascii` is the name of the file containing the ascii logo: this file has to be placed in the _./resources_ folder (more info below). Then we have the colors used to customize the look of the ascii logo when displayed to the screen. The last item `c#` is only required if the Enum name `CSharp` doesn't match the display name `C#` and is used as an input for `-a, --ascii-language <LANGUAGE>` - by default we take the Enum name in lowercase.

Expand Down
2 changes: 1 addition & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::info::deps::package_manager::PackageManager;
use crate::info::info_field::{InfoField, InfoFieldOff};
use crate::info::language::Language;
use crate::info::langs::language::Language;
use crate::ui::image_backends;
use crate::ui::image_backends::ImageBackend;
use crate::ui::printer::SerializationFormat;
Expand Down
98 changes: 1 addition & 97 deletions src/info/language.rs → src/info/langs/language.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
use anyhow::{Context, Result};
use colored::Color;
use regex::Regex;
use serde::Serialize;
use std::collections::HashMap;
use std::env;
use strum::{EnumIter, EnumString, IntoStaticStr};

Expand Down Expand Up @@ -66,7 +63,7 @@ macro_rules! define_languages {
}
}

fn get_all_language_types() -> Vec<tokei::LanguageType> {
pub fn get_all_language_types() -> Vec<tokei::LanguageType> {
vec![ $( tokei::LanguageType::$name ,)* ]
}

Expand Down Expand Up @@ -284,96 +281,3 @@ define_languages! {
{ Zig, "zig.ascii", define_colors!( [Color::Yellow] ) },
{ Zsh, "zsh.ascii", define_colors!( [Color::White] ) },
}

pub fn get_dominant_language(languages_stat_vec: &[(Language, f64)]) -> Language {
languages_stat_vec[0].0.clone()
}

pub fn get_language_statistics(
dir: &str,
ignored_directories: &[String],
include_hidden: bool,
) -> Result<(Vec<(Language, f64)>, usize)> {
let stats = get_statistics(dir, ignored_directories, 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<HashMap<Language, f64>> {
let mut language_distribution = HashMap::new();

for (language_type, language) in languages.iter() {
let mut code = language.code;

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 code == 0 {
continue;
}

language_distribution.insert(Language::from(*language_type), code as f64);
}

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;
}

Some(language_distribution)
}
}

fn get_total_loc(languages: &tokei::Languages) -> usize {
languages.values().collect::<Vec<&tokei::Language>>().iter().fold(0, |sum, val| sum + val.code)
}

fn get_statistics(
dir: &str,
ignored_directories: &[String],
include_hidden: bool,
) -> tokei::Languages {
let mut languages = tokei::Languages::new();
let required_languages = get_all_language_types();
let tokei_config = tokei::Config {
types: Some(required_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_ignored_directories(user_ignored_directories: &[String]) -> Vec<String> {
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
}
99 changes: 99 additions & 0 deletions src/info/langs/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use anyhow::{Context, Result};
use language::{get_all_language_types, Language};
use regex::Regex;
use std::collections::HashMap;

pub mod language;

pub fn get_dominant_language(languages_stat_vec: &[(Language, f64)]) -> Language {
languages_stat_vec[0].0.clone()
}

pub fn get_language_statistics(
dir: &str,
ignored_directories: &[String],
include_hidden: bool,
) -> Result<(Vec<(Language, f64)>, usize)> {
let stats = get_statistics(dir, ignored_directories, 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<HashMap<Language, f64>> {
let mut language_distribution = HashMap::new();

for (language_type, language) in languages.iter() {
let mut code = language.code;

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 code == 0 {
continue;
}

language_distribution.insert(Language::from(*language_type), code as f64);
}

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;
}

Some(language_distribution)
}
}

fn get_total_loc(languages: &tokei::Languages) -> usize {
languages.values().collect::<Vec<&tokei::Language>>().iter().fold(0, |sum, val| sum + val.code)
}

fn get_statistics(
dir: &str,
ignored_directories: &[String],
include_hidden: bool,
) -> tokei::Languages {
let mut languages = tokei::Languages::new();
let required_languages = get_all_language_types();
let tokei_config = tokei::Config {
types: Some(required_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_ignored_directories(user_ignored_directories: &[String]) -> Vec<String> {
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
}
8 changes: 4 additions & 4 deletions src/info/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use colored::{Color, ColoredString, Colorize};
use deps::DependencyDetector;
use git2::Repository;
use head_refs::HeadRefs;
use language::Language;
use langs::language::Language;
use license::Detector;
use repo::Repo;
use serde::ser::SerializeStruct;
Expand All @@ -17,7 +17,7 @@ mod author;
pub mod deps;
mod head_refs;
pub mod info_field;
pub mod language;
pub mod langs;
mod license;
pub mod repo;

Expand Down Expand Up @@ -168,12 +168,12 @@ impl Info {
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) = language::get_language_statistics(
let (languages, lines_of_code) = langs::get_language_statistics(
&workdir,
&config.ignored_directories,
config.include_hidden,
)?;
let dominant_language = language::get_dominant_language(&languages);
let dominant_language = langs::get_dominant_language(&languages);
let ascii_colors = get_ascii_colors(
&config.ascii_language,
&dominant_language,
Expand Down
2 changes: 1 addition & 1 deletion src/ui/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::info::language::Language;
use crate::info::langs::language::Language;
use colored::Color;

pub mod ascii_art;
Expand Down

0 comments on commit 8699d26

Please sign in to comment.