kandil_code 0.1.0

Intelligent development platform (CLI + TUI + Multi-Agent System)
//! Template engine for generating multi-language project structures
//! 
//! Contains functionality for creating project templates from various languages

use anyhow::Result;
use std::collections::HashMap;
use std::fs;
use std::path::Path;

#[derive(Debug, Clone)]
pub struct TemplateEngine {
    templates: HashMap<String, Template>,
}

#[derive(Debug, Clone)]
pub struct Template {
    name: String,
    description: String,
    pub files: Vec<TemplateFile>,
}

impl Template {
    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn description(&self) -> &str {
        &self.description
    }
}

#[derive(Debug, Clone)]
pub struct TemplateFile {
    pub path: String,
    pub content: String,
    is_executable: bool,
}

impl TemplateFile {
    pub fn is_executable(&self) -> bool {
        self.is_executable
    }
}

impl TemplateEngine {
    pub fn new() -> Self {
        let mut engine = Self {
            templates: HashMap::new(),
        };
        
        engine.load_templates();
        engine
    }

    fn load_templates(&mut self) {
        // Load built-in templates
        self.load_flutter_template();
        self.load_python_template();
        self.load_js_template();
        self.load_rust_template();
    }

    fn load_flutter_template(&mut self) {
        let template = Template {
            name: "flutter".to_string(),
            description: "Flutter Clean Architecture project".to_string(),
            files: vec![
                TemplateFile {
                    path: "pubspec.yaml".to_string(),
                    content: "name: {{project_name}}\ndescription: A new Flutter project.\nversion: 1.0.0+1\n\nenvironment:\n  sdk: '>=3.0.0 <4.0.0'\n  flutter: '>=3.0.0'\n\ndependencies:\n  flutter:\n    sdk: flutter\n  get_it: ^7.0.0\n  dio: ^5.0.0\n  flutter_bloc: ^8.0.0\n\nflutter:\n  uses-material-design: true\n".to_string(),
                    is_executable: false,
                },
                TemplateFile {
                    path: "lib/main.dart".to_string(),
                    content: "import 'package:flutter/material.dart';\n\nvoid main() {\n  runApp(const MyApp());\n}\n\nclass MyApp extends StatelessWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      title: 'Flutter Demo',\n      theme: ThemeData(\n        primarySwatch: Colors.blue,\n      ),\n      home: const MyHomePage(title: 'Flutter Demo Home Page'),\n    );\n  }\n}\n\nclass MyHomePage extends StatefulWidget {\n  const MyHomePage({super.key, required this.title});\n\n  final String title;\n\n  @override\n  State<MyHomePage> createState() => _MyHomePageState();\n}\n\nclass _MyHomePageState extends State<MyHomePage> {\n  int _counter = 0;\n\n  void _incrementCounter() {\n    setState(() {\n      _counter++;\n    });\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(\n        title: Text(widget.title),\n      ),\n      body: Center(\n        child: Column(\n          mainAxisAlignment: MainAxisAlignment.center,\n          children: <Widget>[\n            const Text(\n              'You have pushed the button this many times:',\n            ),\n            Text(\n              '$_counter',\n              style: Theme.of(context).textTheme.headlineMedium,\n            ),\n          ],\n        ),\n      ),\n      floatingActionButton: FloatingActionButton(\n        onPressed: _incrementCounter,\n        tooltip: 'Increment',\n        child: const Icon(Icons.add),\n      ),\n    );\n  }\n}\n".to_string(),
                    is_executable: false,
                },
            ],
        };
        self.templates.insert("flutter".to_string(), template);
    }

    fn load_python_template(&mut self) {
        let template = Template {
            name: "python".to_string(),
            description: "Python FastAPI project".to_string(),
            files: vec![
                TemplateFile {
                    path: "requirements.txt".to_string(),
                    content: "fastapi==0.104.1\nuvicorn==0.24.0\npydantic==2.5.0\npython-dotenv==1.0.0\n".to_string(),
                    is_executable: false,
                },
                TemplateFile {
                    path: "app/main.py".to_string(),
                    content: "from fastapi import FastAPI\nimport uvicorn\n\napp = FastAPI(\n    title=\"{{project_name}}\",\n    description=\"A FastAPI application\",\n    version=\"0.1.0\"\n)\n\n@app.get(\"/\")\nasync def root():\n    return {\"message\": \"Welcome to {{project_name}}!\"}\n\n@app.get(\"/health\")\nasync def health_check():\n    return {\"status\": \"healthy\"}\n\nif __name__ == \"__main__\":\n    uvicorn.run(app, host=\"0.0.0.0\", port=8000)\n".to_string(),
                    is_executable: false,
                },
            ],
        };
        self.templates.insert("python".to_string(), template);
    }

    fn load_js_template(&mut self) {
        let template = Template {
            name: "javascript".to_string(),
            description: "JavaScript Next.js project".to_string(),
            files: vec![
                TemplateFile {
                    path: "package.json".to_string(),
                    content: "{\n  \"name\": \"{{project_name}}\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\": \"next build\",\n    \"start\": \"next start\",\n    \"lint\": \"next lint\"\n  },\n  \"dependencies\": {\n    \"react\": \"^18\",\n    \"react-dom\": \"^18\",\n    \"next\": \"^14\"\n  },\n  \"devDependencies\": {\n    \"eslint\": \"^8\",\n    \"eslint-config-next\": \"^14\"\n  }\n}\n".to_string(),
                    is_executable: false,
                },
                TemplateFile {
                    path: "pages/index.js".to_string(),
                    content: "import Head from 'next/head'\nimport Image from 'next/image'\nimport { Inter } from 'next/font/google'\n\nconst inter = Inter({ subsets: ['latin'] })\n\nexport default function Home() {\n  return (\n    <>\n      <Head>\n        <title>{{project_name}}</title>\n        <meta name=\"description\" content=\"Generated by create next app\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n        <link rel=\"icon\" href=\"/favicon.ico\" />\n      </Head>\n      <main>\n        <h1>Welcome to {{project_name}}!</h1>\n      </main>\n    </>\n  )\n}\n".to_string(),
                    is_executable: false,
                },
            ],
        };
        self.templates.insert("javascript".to_string(), template);
    }

    fn load_rust_template(&mut self) {
        let template = Template {
            name: "rust".to_string(),
            description: "Rust CLI project".to_string(),
            files: vec![
                TemplateFile {
                    path: "Cargo.toml".to_string(),
                    content: "[package]\nname = \"{{project_name}}\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\ntokio = { version = \"1.0\", features = [\"full\"] }\nclap = { version = \"4.0\", features = [\"derive\"] }\nanyhow = \"1.0\"\n".to_string(),
                    is_executable: false,
                },
                TemplateFile {
                    path: "src/main.rs".to_string(),
                    content: "use anyhow::Result;\nuse clap::Parser;\n\n#[derive(Parser)]\n#[command(author, version, about, long_about = None)]\nstruct Args {\n    /// Give a name to greet\n    #[arg(short, long, default_value = \"World\")]\n    name: String,\n    /// Return health status\n    #[arg(long, default_value_t = false)]\n    health: bool,\n}\n\n#[tokio::main]\nasync fn main() -> Result<()> {\n    let args = Args::parse();\n    if args.health {\n        println!(\"{\\\"status\\\":\\\"ok\\\"}\");\n    } else {\n        println!(\"Hello, {}!\", args.name);\n    }\n    Ok(())\n}\n".to_string(),
                    is_executable: false,
                },
            ],
        };
        self.templates.insert("rust".to_string(), template);
    }

    pub fn list_templates(&self) -> Vec<(&String, &str)> {
        self.templates
            .iter()
            .map(|(name, template)| (name, template.description()))
            .collect()
    }

    pub fn create_project(&self, template_name: &str, project_path: &str, project_name: &str) -> Result<()> {
        if let Some(template) = self.templates.get(template_name) {
            let project_dir = Path::new(project_path);
            fs::create_dir_all(project_dir)?;

            for file in &template.files {
                // Replace template variables
                let content = file.content.replace("{{project_name}}", project_name);
                
                let file_path = project_dir.join(&file.path);
                if let Some(parent) = file_path.parent() {
                    fs::create_dir_all(parent)?;
                }
                
                fs::write(&file_path, content)?;

                // Set executable permissions if needed (Unix systems)
                #[cfg(unix)]
                if file.is_executable {
                    use std::os::unix::fs::PermissionsExt;
                    let mut perms = fs::metadata(&file_path)?.permissions();
                    perms.set_mode(perms.mode() | 0o111); // Add execute permissions
                    fs::set_permissions(&file_path, perms)?;
                }
            }

            Ok(())
        } else {
            Err(anyhow::anyhow!("Template '{}' not found", template_name))
        }
    }
}