use std::borrow::Cow;
use std::collections::{BTreeMap, HashMap};
pub const MAX_PRODUCERS_VALUE : usize = 32;
pub const MIN_PRODUCERS_VALUE : usize = 1;
pub const MAX_CONSUMERS_VALUE : usize = 128;
pub const MIN_CONSUMERS_VALUE : usize = 1;
#[derive(Debug,PartialEq,Eq,Clone)]
pub struct Target {
pub module: Option<String>,
pub path: String
}
impl Target {
pub fn of(path: impl AsRef<str>) -> Self {
Target { module: None, path: path.as_ref().to_owned() }
}
pub fn named(module: impl AsRef<str>, path: impl AsRef<str>) -> Self {
Target { module: Some(module.as_ref().to_owned()), path: path.as_ref().to_owned() }
}
}
impl Ord for Target {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
crate::engine::targets::path_comparison_key(&self.path)
.cmp(&crate::engine::targets::path_comparison_key(&other.path))
.then_with(|| self.path.cmp(&other.path))
.then_with(|| self.module.cmp(&other.module))
}
}
impl PartialOrd for Target {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl std::fmt::Display for Target {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.module {
Some(name) => write!(formatter, "{name}={}", self.path),
None => write!(formatter, "{}", self.path)
}
}
}
pub type LanguageNames = ScopedByModule<Vec<String>>;
pub type ForcedLanguages = ScopedByModule<HashMap<String,String>>;
#[derive(Debug,PartialEq,Eq,Clone,Default)]
pub struct ScopedByModule<T> {
whole_run: T,
per_module: BTreeMap<String,T>
}
impl<T> ScopedByModule<T> {
pub fn of_the_whole_run(value: T) -> Self {
ScopedByModule { whole_run: value, per_module: BTreeMap::new() }
}
pub fn of(whole_run: T, per_module: impl IntoIterator<Item = (String,T)>) -> Self {
ScopedByModule { whole_run, per_module: per_module.into_iter().collect() }
}
pub fn get_of_the_whole_run(&self) -> &T {
&self.whole_run
}
pub fn get_module_names(&self) -> impl Iterator<Item = &str> {
self.per_module.keys().map(String::as_str)
}
pub fn is_scoped(&self) -> bool {
!self.per_module.is_empty()
}
fn get_declared_by(&self, module: Option<&str>) -> Option<&T> {
module.and_then(|name| self.per_module.get(name))
}
}
impl ScopedByModule<Vec<String>> {
pub(crate) fn get_names_of_module(&self, module: Option<&str>) -> &[String] {
self.get_declared_by(module).unwrap_or(&self.whole_run)
}
pub fn get_all_names(&self) -> Vec<String> {
let mut names = self.whole_run.clone();
for own in self.per_module.values() {
let fresh = own.iter().filter(|name| !names.contains(name)).cloned().collect::<Vec<_>>();
names.extend(fresh);
}
names
}
pub fn is_empty(&self) -> bool {
self.whole_run.is_empty() && self.per_module.values().all(Vec::is_empty)
}
pub fn of_written_form(names: &[String]) -> Self {
let mut scoped = LanguageNames::default();
for name in names {
let (module, name) = split_off_module_scope(name);
match module {
Some(module) => scoped.per_module.entry(module.to_owned()).or_default().push(name.to_owned()),
None => scoped.whole_run.push(name.to_owned())
}
}
scoped
}
pub fn to_written_form(&self) -> Vec<String> {
self.whole_run.iter().cloned()
.chain(self.per_module.iter().flat_map(|(module, names)| names.iter()
.map(|name| format_module_scope(Some(module), name))))
.collect()
}
}
impl ScopedByModule<HashMap<String,String>> {
pub(crate) fn get_rules_of_module(&self, module: Option<&str>) -> Cow<'_, HashMap<String,String>> {
match self.get_declared_by(module) {
None => Cow::Borrowed(&self.whole_run),
Some(own) => {
let mut merged = self.whole_run.clone();
merged.extend(own.iter().map(|(claimed, language)| (claimed.clone(), language.clone())));
Cow::Owned(merged)
}
}
}
pub fn is_empty(&self) -> bool {
self.whole_run.is_empty() && self.per_module.values().all(HashMap::is_empty)
}
pub fn of_written_form(pairs: &HashMap<String,String>) -> Self {
let mut scoped = ForcedLanguages::default();
for (claimed, language) in pairs {
let (module, claimed) = split_off_module_scope(claimed);
match module {
Some(module) => { scoped.per_module.entry(module.to_owned()).or_default()
.insert(claimed.to_owned(), language.clone()); },
None => { scoped.whole_run.insert(claimed.to_owned(), language.clone()); }
}
}
scoped
}
pub fn to_written_form(&self) -> HashMap<String,String> {
self.whole_run.iter().map(|(claimed, language)| (claimed.clone(), language.clone()))
.chain(self.per_module.iter().flat_map(|(module, rules)| rules.iter()
.map(|(claimed, language)| (format_module_scope(Some(module), claimed), language.clone()))))
.collect()
}
}
impl From<Vec<String>> for ScopedByModule<Vec<String>> {
fn from(names: Vec<String>) -> Self {
LanguageNames::of_written_form(&names)
}
}
impl From<HashMap<String,String>> for ScopedByModule<HashMap<String,String>> {
fn from(pairs: HashMap<String,String>) -> Self {
ForcedLanguages::of_written_form(&pairs)
}
}
pub fn split_off_module_scope(text: &str) -> (Option<&str>, &str) {
match text.split_once('/') {
Some((module, value)) if !module.is_empty() && !value.is_empty() => (Some(module), value),
_ => (None, text)
}
}
pub fn format_module_scope(module: Option<&str>, value: &str) -> String {
match module {
Some(module) => format!("{module}/{value}"),
None => value.to_owned()
}
}
#[derive(Debug,PartialEq,Eq,Clone,Copy)]
pub struct Threads {
producers: usize,
consumers: usize
}
impl Threads {
pub fn new(producers: usize, consumers: usize) -> Self {
Threads {
producers: producers.clamp(MIN_PRODUCERS_VALUE, MAX_PRODUCERS_VALUE),
consumers: consumers.clamp(MIN_CONSUMERS_VALUE, MAX_CONSUMERS_VALUE)
}
}
pub fn producers(&self) -> usize {
self.producers
}
pub fn consumers(&self) -> usize {
self.consumers
}
}
impl From<(usize,usize)> for Threads {
fn from(threads: (usize,usize)) -> Self {
Threads::new(threads.0, threads.1)
}
}
impl Default for Threads {
fn default() -> Self {
let threads = num_cpus::get();
Threads {
producers: (threads / 2).clamp(2, MAX_PRODUCERS_VALUE),
consumers: (threads * 4).clamp(8, MAX_CONSUMERS_VALUE)
}
}
}
#[derive(Debug,PartialEq,Clone)]
pub struct EngineConfig {
pub targets: Vec<Target>,
pub exclude_dirs: Vec<String>,
pub languages_of_interest: LanguageNames,
pub excluded_languages: LanguageNames,
pub forced_languages: ForcedLanguages,
pub threads: Threads,
pub should_search_in_dotted: bool,
pub no_gitignore: bool,
pub no_ignore_files: bool,
pub count_keywords: bool,
pub count_minified: bool,
pub count_generated: bool,
pub count_not_code: bool,
pub collect_files: bool,
pub use_heuristics: bool,
pub detect_shebangs: bool
}
impl Default for EngineConfig {
fn default() -> Self {
EngineConfig {
targets: Vec::new(),
exclude_dirs: Vec::new(),
languages_of_interest: LanguageNames::default(),
excluded_languages: LanguageNames::default(),
forced_languages: ForcedLanguages::default(),
threads: Threads::default(),
should_search_in_dotted: false,
no_gitignore: false,
no_ignore_files: false,
count_keywords: true,
count_minified: false,
count_generated: false,
count_not_code: false,
collect_files: false,
use_heuristics: true,
detect_shebangs: true
}
}
}
impl EngineConfig {
pub fn new(targets: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
EngineConfig {
targets: targets.into_iter().map(Target::of).collect(),
..Default::default()
}
}
}