tina4 3.8.13

Tina4 — Unified CLI for Python, PHP, Ruby, and Node.js frameworks
use std::path::Path;

/// Information about a detected Tina4 project.
pub struct ProjectInfo {
    pub language: String,
    #[allow(dead_code)]
    pub version: Option<String>,
}

impl ProjectInfo {
    /// Returns the language-specific CLI binary name.
    pub fn cli_name(&self) -> &str {
        match self.language.as_str() {
            "python" => "tina4python",
            "php" => "tina4php",
            "ruby" => "tina4ruby",
            "nodejs" => "tina4nodejs",
            "tina4js" => "vite",
            _ => "tina4",
        }
    }

    /// Returns the default port for this framework.
    pub fn default_port(&self) -> u16 {
        match self.language.as_str() {
            "python" => 7146,
            "php" => 7145,
            "ruby" => 7147,
            "nodejs" => 7148,
            "tina4js" => 5173,
            _ => 7145,
        }
    }
}

/// Detect the Tina4 project language from files in the current directory.
///
/// Checks for:
///   - pyproject.toml or requirements.txt with tina4 → Python
///   - composer.json with tina4 → PHP
///   - Gemfile or tina4ruby.gemspec → Ruby
///   - package.json with @tina4 → Node.js
pub fn detect_language() -> Option<ProjectInfo> {
    // Python: pyproject.toml or requirements.txt
    if Path::new("pyproject.toml").exists() {
        if let Ok(content) = std::fs::read_to_string("pyproject.toml") {
            if content.contains("tina4") {
                return Some(ProjectInfo {
                    language: "python".into(),
                    version: extract_version_toml(&content),
                });
            }
        }
    }
    if Path::new("requirements.txt").exists() {
        if let Ok(content) = std::fs::read_to_string("requirements.txt") {
            if content.to_lowercase().contains("tina4") {
                return Some(ProjectInfo {
                    language: "python".into(),
                    version: None,
                });
            }
        }
    }
    // Also detect app.py as a Python project
    if Path::new("app.py").exists() {
        return Some(ProjectInfo {
            language: "python".into(),
            version: None,
        });
    }

    // PHP: composer.json
    if Path::new("composer.json").exists() {
        if let Ok(content) = std::fs::read_to_string("composer.json") {
            if content.contains("tina4") {
                return Some(ProjectInfo {
                    language: "php".into(),
                    version: extract_version_json(&content, "version"),
                });
            }
        }
    }

    // Ruby: Gemfile or gemspec
    if Path::new("Gemfile").exists() {
        if let Ok(content) = std::fs::read_to_string("Gemfile") {
            if content.contains("tina4") {
                return Some(ProjectInfo {
                    language: "ruby".into(),
                    version: None,
                });
            }
        }
    }
    if Path::new("tina4ruby.gemspec").exists() {
        return Some(ProjectInfo {
            language: "ruby".into(),
            version: None,
        });
    }

    // Node.js / tina4js: package.json
    if Path::new("package.json").exists() {
        if let Ok(content) = std::fs::read_to_string("package.json") {
            // tina4js (frontend) — has "tina4js" dep but NOT "tina4-nodejs"
            if content.contains("\"tina4js\"") && !content.contains("tina4-nodejs") && !content.contains("tina4nodejs") {
                return Some(ProjectInfo {
                    language: "tina4js".into(),
                    version: extract_version_json(&content, "version"),
                });
            }
            if content.contains("@tina4") || content.contains("tina4-nodejs") || content.contains("tina4nodejs") {
                return Some(ProjectInfo {
                    language: "nodejs".into(),
                    version: extract_version_json(&content, "version"),
                });
            }
        }
    }
    // Also detect app.ts as a Node.js project
    if Path::new("app.ts").exists() {
        return Some(ProjectInfo {
            language: "nodejs".into(),
            version: None,
        });
    }

    // Also detect app.rb as a Ruby project
    if Path::new("app.rb").exists() {
        return Some(ProjectInfo {
            language: "ruby".into(),
            version: None,
        });
    }

    // Also detect index.php as a PHP project
    if Path::new("index.php").exists() {
        return Some(ProjectInfo {
            language: "php".into(),
            version: None,
        });
    }

    None
}

fn extract_version_toml(content: &str) -> Option<String> {
    for line in content.lines() {
        let trimmed = line.trim();
        if trimmed.starts_with("version") && trimmed.contains('=') {
            let val = trimmed.split('=').nth(1)?.trim();
            return Some(val.trim_matches('"').to_string());
        }
    }
    None
}

fn extract_version_json(content: &str, key: &str) -> Option<String> {
    // Simple JSON value extraction without a full parser
    let pattern = format!("\"{}\"", key);
    let pos = content.find(&pattern)?;
    let rest = &content[pos + pattern.len()..];
    let colon = rest.find(':')?;
    let after_colon = rest[colon + 1..].trim_start();
    if let Some(stripped) = after_colon.strip_prefix('"') {
        let end = stripped.find('"')?;
        return Some(stripped[..end].to_string());
    }
    None
}