diff --git a/Cargo.lock b/Cargo.lock index c809797..ac9024c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2390,6 +2390,8 @@ dependencies = [ "directories", "gio", "glib", + "serde", + "serde_json", "tokei", "tokio", "walkdir", diff --git a/Makefile b/Makefile index 7db3c55..2d73930 100644 --- a/Makefile +++ b/Makefile @@ -156,10 +156,4 @@ docs: ## Build and open documentation # Development tools installation tools: ## Install useful development tools cargo install cargo-watch cargo-audit cargo-machete cargo-flamegraph cargo-deb cargo-outdated - @echo "Development tools installed!" - -nix-install: - nix profile install . - -nix-reinstall: - nix profile install --reinstall . \ No newline at end of file + @echo "Development tools installed!" \ No newline at end of file diff --git a/waycast-plugins/Cargo.toml b/waycast-plugins/Cargo.toml index 7e73937..5af02c7 100644 --- a/waycast-plugins/Cargo.toml +++ b/waycast-plugins/Cargo.toml @@ -19,6 +19,8 @@ tokio = { version = "1.0", features = [ ] } walkdir = "2.5.0" tokei = "12.1.2" +serde = "1.0.219" +serde_json = "1.0.143" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(rust_analyzer)'] } diff --git a/waycast-plugins/src/main.rs b/waycast-plugins/src/main.rs index 9042a1b..d1f63bb 100644 --- a/waycast-plugins/src/main.rs +++ b/waycast-plugins/src/main.rs @@ -1,46 +1,33 @@ use std::{collections::BTreeMap, path::PathBuf}; use tokei::{Config, LanguageType, Languages}; - -fn lang_breakdown(paths: &[&str], excluded: &[&str]) -> Vec<(LanguageType, usize, f64)> { - let mut langs = Languages::new(); - let cfg = Config::default(); - langs.get_statistics(paths, excluded, &cfg); - - let total_code: usize = langs.iter().map(|(_, l)| l.code).sum(); - let mut rows: Vec<_> = langs - .iter() - .map(|(lt, l)| { - ( - *lt, - l.code, - if total_code > 0 { - (l.code as f64) * 100.0 / (total_code as f64) - } else { - 0.0 - }, - ) - }) - .collect(); - rows.sort_by_key(|(_, lines, _)| std::cmp::Reverse(*lines)); - rows -} +use waycast_plugins::projects::{ + framework_detector::{self, FrameworkDetector}, + type_scanner::TypeScanner, +}; pub fn main() { + let scanner = TypeScanner::new(); + let framework_detector = FrameworkDetector::new(); if let Ok(entries) = std::fs::read_dir(PathBuf::from("/home/javi/projects")) { for e in entries .into_iter() .filter(|e| e.is_ok()) .map(|e| e.unwrap()) + .filter(|e| e.path().is_dir()) { - let langs = lang_breakdown(&[e.path().to_str().unwrap()], &[]); + let fw = framework_detector.detect(e.path().to_string_lossy().to_string().as_str()); - let top = langs - .iter() - .map(|(l, _, _)| l.to_owned()) - .take(3) - .collect::>(); + if let Some(name) = fw { + println!("{}: {}", e.path().display(), name); + } else { + println!("{}: {}", e.path().display(), "NONE"); + } + // let langs = scanner.scan(e.path(), Some(3)); + // // let langs = lang_breakdown(&[e.path().to_str().unwrap()], &[]); - println!("{}: {:?}", e.path().display(), top); + // let top: Vec = langs.iter().map(|l| l.name.to_owned()).collect(); + + // println!("{}: {:?}", e.path().display(), top); } } } diff --git a/waycast-plugins/src/projects/framework_detector.rs b/waycast-plugins/src/projects/framework_detector.rs new file mode 100644 index 0000000..cad61d4 --- /dev/null +++ b/waycast-plugins/src/projects/framework_detector.rs @@ -0,0 +1,84 @@ +use std::path::{Path, PathBuf}; + +pub enum Framework { + Laravel, + Rails, + Vue, + NextJS, + Ansible, +} + +fn has_file>(project_path: P, file: P) -> bool { + PathBuf::from(project_path.as_ref()).join(file).exists() +} + +fn read_json_config>(project_path: P, file: P) -> Option { + let pb = PathBuf::from(project_path.as_ref()); + if let Ok(text) = std::fs::read_to_string(pb.join(file)) { + if let Ok(v) = serde_json::from_str::(&text) { + return Some(v); + } + } else { + return None; + } + + None +} + +trait FrameworkHeuristics: Sync { + fn name(&self) -> &'static str; + fn matches(&self, project_path: &str) -> bool; +} + +struct Laravel; +impl FrameworkHeuristics for Laravel { + fn name(&self) -> &'static str { + "Laravel" + } + + fn matches(&self, project_path: &str) -> bool { + // Check for composer.json + if !has_file(project_path, "composer.json") { + return false; + } + + // If composer.json has "laravel/framework" + // we can say yes immediately + if let Some(cfg) = read_json_config(project_path, "composer.json") { + let requires_laravel = cfg + .get("require") + .and_then(|r| r.get("laravel/framework")) + .is_some(); + if requires_laravel { + return true; + } + } + + false + } +} + +pub struct FrameworkDetector { + heuristics: &'static [&'static dyn FrameworkHeuristics], +} + +static LARAVEL: Laravel = Laravel; +static HEURISTICS: &[&dyn FrameworkHeuristics] = &[&LARAVEL]; + +impl FrameworkDetector { + pub fn new() -> FrameworkDetector { + FrameworkDetector { + heuristics: HEURISTICS, + } + } + + pub fn detect(&self, project_path: &str) -> Option { + for h in self.heuristics { + if h.matches(project_path) { + return Some(String::from(h.name())); + } + } + + None + } +} diff --git a/waycast-plugins/src/projects.rs b/waycast-plugins/src/projects/mod.rs similarity index 95% rename from waycast-plugins/src/projects.rs rename to waycast-plugins/src/projects/mod.rs index c6d15d8..b77e303 100644 --- a/waycast-plugins/src/projects.rs +++ b/waycast-plugins/src/projects/mod.rs @@ -1,10 +1,5 @@ -// TODO: Use the user's preferred editor. -// This should just be in the config when I implement -// that eventually since figuring out every editor's -// launch option would be a pain. The user can just -// configure launch_command and pass a parameter. -// Example: code -n {path} -// and I'll just regex in the path. +pub mod framework_detector; +pub mod type_scanner; // TODO: Project type detection and icon use std::{ collections::HashSet, diff --git a/waycast-plugins/src/projects/type_scanner.rs b/waycast-plugins/src/projects/type_scanner.rs new file mode 100644 index 0000000..3741f9e --- /dev/null +++ b/waycast-plugins/src/projects/type_scanner.rs @@ -0,0 +1,63 @@ +use std::path::Path; + +use tokei::{Config, Language, LanguageType, Languages}; + +pub struct ProjectLanguage { + pub name: String, + pub percentage: f64, +} + +pub struct TypeScanner { + tokei_config: Config, + ignore_langs: [LanguageType; 5], +} + +impl TypeScanner { + pub fn new() -> TypeScanner { + TypeScanner { + tokei_config: Config::default(), + ignore_langs: [ + LanguageType::Css, + LanguageType::Json, + LanguageType::Markdown, + LanguageType::CppHeader, + LanguageType::CHeader, + ], + } + } + + // Scan a project for languages used. Limit gives the + // top [limit] entries + pub fn scan>(&self, path: P, limit: Option) -> Vec { + let mut langs = Languages::new(); + langs.get_statistics(&[path], &[], &self.tokei_config); + + let total_code: usize = langs.iter().map(|(_, l)| l.code).sum(); + let mut rows: Vec = langs + .iter() + .map(|(lt, l)| { + ( + *lt, + l.code, + if total_code > 0 { + (l.code as f64) * 100.0 / (total_code as f64) + } else { + 0.0 + }, + ) + }) + .filter(|(l, _, _)| !self.ignore_langs.contains(l)) + .map(|(lang, _, percent)| ProjectLanguage { + name: String::from(lang.name()), + percentage: percent, + }) + .collect(); + rows.sort_by(|a, b| b.percentage.partial_cmp(&a.percentage).unwrap()); + + if let Some(l) = limit { + rows.truncate(l); + } + + rows + } +}