use crate::config::{preset_choice_to_config, CciConfig};
use crate::editor::registry::build_registry;
use crate::error::Result;
use crate::generator::MultiPresetGenerator;
use anyhow::{bail, Context};
use colored::Colorize;
use std::path::PathBuf;
use std::sync::Arc;
pub fn handle_generate(config_path: &str, platform_arg: Option<String>, force: bool) -> Result<()> {
use crate::detection::DetectorRegistry;
use crate::editor::state::Platform;
println!("{} {}", "Loading".cyan().bold(), config_path);
let ron_str = std::fs::read_to_string(config_path)
.with_context(|| format!("Failed to read config file: {}", config_path))?;
println!("{}", "Parsing RON configuration...".cyan().bold());
let config: CciConfig = ron::from_str(&ron_str)
.with_context(|| "Failed to parse RON config. Check syntax and structure.")?;
if config.presets.is_empty() {
bail!("No presets defined in configuration file");
}
let working_dir = std::path::PathBuf::from(".");
let detector_registry = DetectorRegistry::new();
let detection = detector_registry.detect(&working_dir)?;
let platform = if let Some(p) = platform_arg {
match p.to_lowercase().as_str() {
"github" => Platform::GitHub,
"gitea" => Platform::Gitea,
"gitlab" => Platform::GitLab,
"circleci" => Platform::CircleCI,
"jenkins" => Platform::Jenkins,
_ => Platform::GitHub,
}
} else {
Platform::GitHub };
println!(
"{} {} preset(s) for platform {}",
"Found".green().bold(),
config.presets.len(),
format!("{:?}", platform).yellow()
);
let registry = Arc::new(build_registry());
let mut preset_configs = Vec::new();
for preset_choice in config.presets {
let (preset_id, preset_config) = preset_choice_to_config(preset_choice)
.with_context(|| "Failed to convert preset configuration")?;
println!(" {} {}", "•".blue(), preset_id);
preset_configs.push((preset_id, preset_config));
}
println!("\n{}", "Generating CI configurations...".cyan().bold());
let language_version = detection
.language_version
.unwrap_or_else(|| "stable".to_string());
let generator = MultiPresetGenerator::new(
preset_configs,
registry,
platform,
language_version,
);
let outputs = generator
.generate_all()
.with_context(|| "Failed to generate CI configurations")?;
let base_path = PathBuf::from(".");
for (filename, content) in outputs {
let output_path = base_path.join(&filename);
if output_path.exists() && !force {
bail!(
"File exists: {}. Use --force to overwrite",
output_path.display()
);
}
if let Some(parent) = output_path.parent() {
std::fs::create_dir_all(parent).with_context(|| {
format!("Failed to create directory: {}", parent.display())
})?;
}
std::fs::write(&output_path, content)
.with_context(|| format!("Failed to write file: {}", output_path.display()))?;
println!(
" {} {}",
"✓".green().bold(),
output_path.display().to_string().yellow()
);
}
println!("\n{}", "Done!".green().bold());
Ok(())
}
pub fn handle_validate(config_path: &str) -> Result<()> {
println!("{} {}", "Validating".cyan().bold(), config_path);
let ron_str = std::fs::read_to_string(config_path)
.with_context(|| format!("Failed to read config file: {}", config_path))?;
let config: CciConfig = ron::from_str(&ron_str).with_context(|| {
"Failed to parse RON config. Check syntax and structure:\n\
- Ensure all fields are properly formatted\n\
- Check for missing commas\n\
- Verify enum variants match expected values"
})?;
if config.presets.is_empty() {
bail!("Validation failed: No presets defined in configuration");
}
println!("\n{}", "Configuration is valid!".green().bold());
println!(" Presets: {}", config.presets.len());
for (idx, preset) in config.presets.iter().enumerate() {
let preset_name = match preset {
crate::config::PresetChoice::Python(_) => "Python",
crate::config::PresetChoice::Rust(_) => "Rust",
crate::config::PresetChoice::GoApp(_) => "Go App",
crate::config::PresetChoice::Docker(_) => "Docker",
};
println!(" {}. {}", idx + 1, preset_name);
}
Ok(())
}
pub fn handle_detect(dir: &str) -> Result<()> {
use crate::detection::DetectorRegistry;
use crate::editor::registry::build_registry;
use std::path::PathBuf;
let working_dir = PathBuf::from(dir);
println!("{}", "Detecting project type...".cyan().bold());
println!();
let detector_registry = DetectorRegistry::new();
let detection = match detector_registry.detect(&working_dir) {
Ok(d) => d,
Err(_) => {
println!("{}", "✗ No project type detected".red().bold());
println!();
println!("This directory doesn't appear to contain a recognized project type.");
println!("Supported project types:");
println!(" • Rust (Cargo.toml)");
println!(" • Python (pyproject.toml, setup.py, requirements.txt)");
println!(" • Go (go.mod)");
println!(" • Docker (Dockerfile, docker-compose.yml)");
return Ok(());
}
};
println!("{} {}", "✓ Project Type:".green().bold(), detection.project_type);
if let Some(version) = &detection.language_version {
println!(" {} {}", "Language Version:".dimmed(), version);
}
if !detection.metadata.is_empty() {
println!();
println!("{}", "Metadata:".cyan().bold());
for (key, value) in &detection.metadata {
println!(" {} {}", format!("{}:", key).dimmed(), value);
}
}
println!();
println!("{}", "Checking for existing CI configurations...".cyan().bold());
let ci_files = vec![
(".github/workflows", "GitHub Actions"),
(".gitea/workflows", "Gitea Actions"),
(".gitlab-ci.yml", "GitLab CI"),
(".circleci/config.yml", "CircleCI"),
("Jenkinsfile", "Jenkins"),
];
let mut found_ci = false;
for (path, platform) in ci_files {
let full_path = working_dir.join(path);
if full_path.exists() {
println!(" {} {}", "✓".green(), platform);
found_ci = true;
}
}
if !found_ci {
println!(" {} No existing CI configurations found", "ℹ".blue());
}
println!();
println!("{}", "Matching presets for this project:".cyan().bold());
let registry = build_registry();
let mut matching_presets = Vec::new();
let mut available_presets = Vec::new();
for preset in registry.all() {
if preset.matches_project(&detection.project_type, &working_dir) {
matching_presets.push(preset);
} else {
available_presets.push(preset);
}
}
if matching_presets.is_empty() {
println!(" {} No presets match this project type", "ℹ".blue());
} else {
for preset in &matching_presets {
println!(" {} {}", "✓".green().bold(), preset.preset_name());
println!(" {}", preset.preset_description().dimmed());
}
}
if !available_presets.is_empty() {
println!();
println!("{}", "Other available presets:".dimmed());
for preset in &available_presets {
println!(" {} {}", "○".dimmed(), preset.preset_name().dimmed());
println!(" {}", preset.preset_description().dimmed());
}
}
println!();
println!("{}", "Next steps:".cyan().bold());
if matching_presets.is_empty() {
println!(" • Run {} to configure CI for this project", "cci editor".yellow());
} else {
println!(" • Run {} to interactively configure CI", "cci editor".yellow());
println!(" • Or create a {} file and run {}", "cci.ron".yellow(), "cci generate".yellow());
}
Ok(())
}