composition_cli/core/
get_composition.rs1use rayon::prelude::*;
2use std::{collections::HashMap, fs};
3use walkdir::DirEntry;
4
5use crate::context::{AppContext, config::Tracked};
6
7#[derive(Debug)]
8pub struct CompositionEntry {
9 pub tracked: Tracked,
10 pub line_count: usize,
11 pub percentage: f32,
12}
13
14pub fn get_composition(app_context: &AppContext, entries: Vec<DirEntry>) -> Vec<CompositionEntry> {
15 let line_counts_by_extension: HashMap<String, usize> = entries
17 .par_iter()
18 .filter_map(|entry| {
19 let ext = entry.path().extension()?.to_str()?.to_lowercase();
20 let lines = count_lines(entry.path(), app_context, &ext)?;
21 Some((ext, lines))
22 })
23 .fold(
24 || HashMap::new(),
25 |mut map, (ext, lines)| {
26 *map.entry(ext).or_insert(0) += lines;
27 map
28 },
29 )
30 .reduce(
31 || HashMap::new(),
32 |mut map1, map2| {
33 for (ext, lines) in map2 {
34 *map1.entry(ext).or_insert(0) += lines;
35 }
36 map1
37 },
38 );
39
40 let composition: Vec<CompositionEntry> = app_context
41 .config
42 .tracked
43 .iter()
44 .filter_map(|tracked| {
45 let total_lines: usize = tracked
46 .extensions
47 .iter()
48 .filter_map(|ext| line_counts_by_extension.get(ext))
49 .sum();
50
51 if total_lines == 0 {
52 return None;
53 }
54
55 Some(CompositionEntry {
56 tracked: Tracked {
57 display: tracked.display.clone(),
58 extensions: tracked.extensions.clone(),
59 color: tracked.color.clone(),
60 excluded_patterns: tracked.excluded_patterns.clone(),
61 compiled_excluded_patterns: tracked.compiled_excluded_patterns.clone(),
62 },
63 line_count: total_lines,
64 percentage: 0.0,
65 })
66 })
67 .collect();
68
69 composition
70}
71
72fn count_lines(path: &std::path::Path, app_context: &AppContext, ext: &str) -> Option<usize> {
73 let content = fs::read_to_string(path).ok()?;
74
75 let tracked = app_context
76 .config
77 .tracked
78 .iter()
79 .find(|t| t.extensions.iter().any(|e| e == ext))?;
80
81 let count = content
82 .lines()
83 .filter(|line| {
84 if app_context.config.ignore_empty_lines && line.trim().is_empty() {
85 return false;
86 }
87
88 for pattern in &app_context.config.compiled_excluded_patterns {
90 if pattern.is_match(line) {
91 return false;
92 }
93 }
94
95 for pattern in &tracked.compiled_excluded_patterns {
97 if pattern.is_match(line) {
98 return false;
99 }
100 }
101
102 true
103 })
104 .count();
105
106 Some(count)
107}