use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum MapError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("File not found: {0}")]
FileNotFound(PathBuf),
#[error("Unsupported language: {0}")]
UnsupportedLanguage(String),
#[error("Parse error: {0}")]
ParseError(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub enum Language {
Rust,
Python,
JavaScript,
TypeScript,
Go,
}
impl Language {
pub fn from_extension(ext: &str) -> Option<Self> {
match ext {
"rs" => Some(Language::Rust),
"py" => Some(Language::Python),
"js" | "jsx" => Some(Language::JavaScript),
"ts" | "tsx" => Some(Language::TypeScript),
"go" => Some(Language::Go),
_ => None,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Language::Rust => "rust",
Language::Python => "python",
Language::JavaScript => "javascript",
Language::TypeScript => "typescript",
Language::Go => "go",
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Config {
pub format: OutputFormat,
pub language: Option<Language>,
pub max_depth: usize,
}
impl Default for Config {
fn default() -> Self {
Self {
format: OutputFormat::Json,
language: None,
max_depth: 10,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum OutputFormat {
Json,
Text,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Symbol {
pub name: String,
pub kind: SymbolKind,
pub range: FileRange,
pub file: PathBuf,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SymbolKind {
Module,
Function,
Class,
Struct,
Interface,
Method,
Property,
Variable,
Constant,
Enum,
Trait,
Impl,
Other(String),
}
impl SymbolKind {
pub fn as_str(&self) -> &str {
match self {
SymbolKind::Module => "module",
SymbolKind::Function => "function",
SymbolKind::Class => "class",
SymbolKind::Struct => "struct",
SymbolKind::Interface => "interface",
SymbolKind::Method => "method",
SymbolKind::Property => "property",
SymbolKind::Variable => "variable",
SymbolKind::Constant => "constant",
SymbolKind::Enum => "enum",
SymbolKind::Trait => "trait",
SymbolKind::Impl => "impl",
SymbolKind::Other(s) => s,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct FileRange {
pub start_line: usize,
pub start_col: usize,
pub end_line: usize,
pub end_col: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RepoMap {
pub root: PathBuf,
pub symbols: Vec<Symbol>,
pub files: Vec<FileInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileInfo {
pub path: PathBuf,
pub language: Option<String>,
pub lines: usize,
}
pub fn generate_map(path: &Path, config: Config) -> Result<RepoMap, MapError> {
if !path.exists() {
return Err(MapError::FileNotFound(path.to_path_buf()));
}
if !path.is_dir() {
return Err(MapError::ParseError("Path must be a directory".to_string()));
}
let mut engine = crate::repo_map::RepoMap::new(path);
engine.rebuild();
let mut symbols = Vec::new();
let mut files = Vec::new();
let mut seen_files = std::collections::HashSet::new();
for (file_path, file_syms) in engine.all_symbols() {
let ext = file_path.extension().and_then(|e| e.to_str()).unwrap_or("");
if let Some(filter_lang) = config.language {
let file_lang = Language::from_extension(ext);
if file_lang != Some(filter_lang) {
continue;
}
}
for sym in file_syms {
let kind = match sym.kind {
crate::repo_map::parser::SymbolKind::Function => SymbolKind::Function,
crate::repo_map::parser::SymbolKind::Struct => SymbolKind::Struct,
crate::repo_map::parser::SymbolKind::Enum => SymbolKind::Enum,
crate::repo_map::parser::SymbolKind::Trait => SymbolKind::Trait,
crate::repo_map::parser::SymbolKind::Impl => SymbolKind::Impl,
crate::repo_map::parser::SymbolKind::Const => SymbolKind::Constant,
crate::repo_map::parser::SymbolKind::Type => SymbolKind::Other("type".into()),
crate::repo_map::parser::SymbolKind::Mod => SymbolKind::Module,
crate::repo_map::parser::SymbolKind::Macro => SymbolKind::Other("macro".into()),
};
symbols.push(Symbol {
name: sym.name.clone(),
kind,
range: FileRange {
start_line: sym.line,
start_col: 0,
end_line: sym.line,
end_col: 0,
},
file: file_path.clone(),
});
}
if seen_files.insert(file_path.clone()) {
let lines = std::fs::read_to_string(file_path)
.map(|c| c.lines().count())
.unwrap_or(0);
files.push(FileInfo {
path: file_path.clone(),
language: Language::from_extension(ext).map(|l| l.as_str().to_string()),
lines,
});
}
}
Ok(RepoMap {
root: path.to_path_buf(),
symbols,
files,
})
}