jump-start 0.1.0

The CLI for jump-start: A shortcut to your favorite code
Documentation
use crate::Config;
use crate::LocalStarterGroupLookup;
use crate::config::get_default_instance;
use crate::starter::get_starter_command;
use crate::starter::parse_starters;
use anyhow::Result;
use std::env;
use std::fs;
use std::path::Path;

fn generate_readme_section(groups: &LocalStarterGroupLookup) -> String {
    let mut output: Vec<String> = Vec::new();

    output.push("\n<!-- NOTE: The starters section of this readme is auto-generated by .github/workflows/deploy.yml -->".to_string());
    output.push("\nJump to group:".to_string());

    for group_name in groups.keys() {
        output.push(format!("- [{}](#{})", group_name, group_name));
    }

    output.push("\n---\n".to_string());

    for (group_name, group_data) in groups {
        output.push(format!("### {}\n", group_name));

        for data in group_data {
            output.push(format!("{}/**{}**", data.group, data.name));

            let github_username = env::var("GITHUB_USERNAME").unwrap_or_default();
            let github_repo = env::var("GITHUB_REPO").unwrap_or_default();
            let degit_mode = env::var("DEGIT_MODE").unwrap_or_default();

            output.push(format!(
                "
```
{}
```
",
                get_starter_command(data, &github_username, &github_repo, &degit_mode)
            ));

            if let Some(config) = &data.config {
                if let Some(description) = &config.description {
                    if !description.is_empty() {
                        output.push(description.clone());
                    }
                }
            }

            output.push("---".to_string());
        }
    }

    output.join("\n")
}

fn rewrite_readme_section(existing_content: &str, section: &str, new_content: &str) -> String {
    let lines: Vec<&str> = existing_content.split('\n').collect();
    let mut rewritten_lines: Vec<String> = Vec::new();
    let mut is_in_section = false;

    for line in lines {
        if line == section {
            is_in_section = true;
            rewritten_lines.push(line.to_string());
            rewritten_lines.push("".to_string());
            rewritten_lines.push(new_content.to_string());
            rewritten_lines.push("".to_string());
        } else {
            if line.starts_with("## ") {
                is_in_section = false;
            }

            if !is_in_section {
                rewritten_lines.push(line.to_string());
            }
        }
    }

    rewritten_lines.join("\n")
}

pub fn update_readme(config: Config) -> Result<()> {
    let instance = get_default_instance(&config);
    println!("Using instance {} ({:?})", instance.name, instance.path);

    let readme_path = Path::new(&instance.path).join("README.md");

    let groups = parse_starters(&instance.path)?;
    let starters_section = generate_readme_section(&groups);

    let existing_readme = fs::read_to_string(&readme_path)
        .map_err(|e| anyhow::anyhow!("Failed to read README.md: {}", e))?;

    let updated_readme = rewrite_readme_section(&existing_readme, "## Starters", &starters_section);

    fs::write(&readme_path, updated_readme)
        .map_err(|e| anyhow::anyhow!("Failed to write updated README.md: {}", e))?;

    Ok(())
}