leankg 0.16.7

Lightweight Knowledge Graph for AI-Assisted Development
Documentation
#![allow(dead_code)]
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;

#[allow(dead_code)]
#[derive(Debug, thiserror::Error)]
pub enum TemplateError {
    #[error("Template not found: {0}")]
    NotFound(String),
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),
    #[error("Render error: {0}")]
    Render(String),
}

pub struct TemplateEngine {
    templates_dir: PathBuf,
}

#[allow(dead_code)]
impl TemplateEngine {
    pub fn new(templates_dir: PathBuf) -> Self {
        Self { templates_dir }
    }

    pub fn load_template(&self, name: &str) -> Result<String, TemplateError> {
        let template_path = self.templates_dir.join(format!("{}.tmpl", name));
        if template_path.exists() {
            Ok(fs::read_to_string(&template_path)?)
        } else {
            Err(TemplateError::NotFound(format!(
                "Template '{}' not found at {}",
                name,
                template_path.display()
            )))
        }
    }

    pub fn render_template(template: &str, variables: &HashMap<String, String>) -> String {
        let mut output = template.to_string();
        for (key, value) in variables {
            let placeholder = format!("{{{{{}}}}}", key);
            output = output.replace(&placeholder, value);
        }
        output
    }

    pub fn render_element_template(
        &self,
        template_name: &str,
        qualified_name: &str,
        element_type: &str,
        relationships: &[String],
    ) -> Result<String, TemplateError> {
        let template = self.load_template(template_name)?;

        let rel_str = if relationships.is_empty() {
            String::new()
        } else {
            relationships.iter().map(|r| format!("- {}\n", r)).collect()
        };

        let mut variables = HashMap::new();
        variables.insert("qualified_name".to_string(), qualified_name.to_string());
        variables.insert("element_type".to_string(), element_type.to_string());
        variables.insert("relationships".to_string(), rel_str);

        Ok(Self::render_template(&template, &variables))
    }

    pub fn render_agents_template(elements: &[String]) -> String {
        let mut output = String::from("# Agent Guidelines for LeanKG\n\n");
        output.push_str("## Codebase Structure\n\n");
        output.push_str("```\n");

        for element in elements {
            output.push_str(&format!("- {}\n", element));
        }

        output.push_str("```\n");
        output
    }

    pub fn render_claude_template(context: &str) -> String {
        let mut output = String::from("# CLAUDE.md\n\n");
        output.push_str("## Project Context\n\n");
        output.push_str(context);
        output.push('\n');
        output
    }

    pub fn render_file_summary(
        file_path: &str,
        elements: &[String],
        relationships: &[String],
    ) -> String {
        let mut output = String::new();
        output.push_str(&format!("# {}\n\n", file_path));
        output.push_str("## Elements\n\n");
        for elem in elements {
            output.push_str(&format!("- {}\n", elem));
        }
        output.push_str("\n## Relationships\n\n");
        for rel in relationships {
            output.push_str(&format!("- {}\n", rel));
        }
        output
    }

    pub fn save_template(&self, name: &str, content: &str) -> Result<(), TemplateError> {
        let template_path = self.templates_dir.join(format!("{}.tmpl", name));
        fs::create_dir_all(&self.templates_dir)?;
        fs::write(&template_path, content)?;
        Ok(())
    }

    pub fn list_templates(&self) -> Result<Vec<String>, TemplateError> {
        let mut templates = Vec::new();
        if self.templates_dir.exists() {
            for entry in fs::read_dir(&self.templates_dir)? {
                let entry = entry?;
                let path = entry.path();
                if path.extension().is_some_and(|ext| ext == "tmpl") {
                    if let Some(stem) = path.file_stem() {
                        templates.push(stem.to_string_lossy().to_string());
                    }
                }
            }
        }
        Ok(templates)
    }

    pub fn render_custom_template(
        &self,
        template_name: &str,
        variables: &HashMap<String, String>,
    ) -> Result<String, TemplateError> {
        let template = self.load_template(template_name)?;
        Ok(Self::render_template(&template, variables))
    }

    pub fn get_default_agents_template() -> &'static str {
        r#"# Agent Guidelines for {{project_name}}

## Project Overview

{{project_description}}

## Build Commands

### Standard Build
```bash
cargo build                    # Debug build
cargo build --release          # Release build
```

### Testing
```bash
cargo test                     # Run all tests
cargo test <test_name>         # Run specific test
cargo test --package <pkg>     # Test specific package
cargo test -- --nocapture      # Show println output during tests
```

### Code Quality
```bash
cargo fmt                      # Format code
cargo fmt -- --check           # Check formatting without changes
cargo clippy                   # Run linter
cargo clippy -- -D warnings    # Treat warnings as errors
```

## Code Structure Overview

This codebase contains {{element_count}} elements and {{relationship_count}} relationships.

### Key Modules

{{modules}}

### Files

{{files}}

### Functions

{{functions}}

## Context Guidelines

{{context_guidelines}}
"#
    }

    pub fn get_default_claude_template() -> &'static str {
        r#"# CLAUDE.md

## Project Overview

{{project_description}}

## Architecture Decisions

{{architecture_decisions}}

## Context Statistics

- **Total elements**: {{element_count}}
- **Total relationships**: {{relationship_count}}
- **Business logic annotations**: {{annotation_count}}

## Key Files

{{key_files}}

## Context Guidelines for AI

{{context_guidelines}}

## Business Logic Annotations

{{annotations}}
"#
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_render_agents_template_empty() {
        let out = TemplateEngine::render_agents_template(&[]);
        assert!(out.contains("# Agent Guidelines"));
        assert!(out.contains("## Codebase Structure"));
    }

    #[test]
    fn test_render_agents_template_with_elements() {
        let elements = vec!["foo".to_string(), "bar".to_string()];
        let out = TemplateEngine::render_agents_template(&elements);
        assert!(out.contains("- foo"));
        assert!(out.contains("- bar"));
    }

    #[test]
    fn test_render_claude_template() {
        let out = TemplateEngine::render_claude_template("test context");
        assert!(out.contains("# CLAUDE.md"));
        assert!(out.contains("test context"));
    }

    #[test]
    fn test_render_file_summary() {
        let elements = vec!["func1".to_string(), "func2".to_string()];
        let relationships = vec!["imports: mod".to_string()];
        let out = TemplateEngine::render_file_summary("src/main.rs", &elements, &relationships);
        assert!(out.contains("# src/main.rs"));
        assert!(out.contains("- func1"));
        assert!(out.contains("- imports: mod"));
    }

    #[test]
    fn test_render_template_with_variables() {
        let template = "Hello {{name}}, you have {{count}} messages.";
        let mut vars = HashMap::new();
        vars.insert("name".to_string(), "Alice".to_string());
        vars.insert("count".to_string(), "5".to_string());
        let out = TemplateEngine::render_template(template, &vars);
        assert_eq!(out, "Hello Alice, you have 5 messages.");
    }

    #[test]
    fn test_get_default_agents_template() {
        let t = TemplateEngine::get_default_agents_template();
        assert!(t.contains("# Agent Guidelines for {{project_name}}"));
        assert!(t.contains("## Build Commands"));
        assert!(t.contains("cargo build"));
    }

    #[test]
    fn test_get_default_claude_template() {
        let t = TemplateEngine::get_default_claude_template();
        assert!(t.contains("# CLAUDE.md"));
        assert!(t.contains("## Architecture Decisions"));
        assert!(t.contains("{{element_count}}"));
    }
}