cargo-edit-toml 0.0.2

A small tool that opens the current project's `Cargo.toml` manifest file using the application provided by either the `$EDITOR` environment variable or the `--editor` argument.
use std::env;
use std::process::{Command, Stdio};

use clap::{Parser, builder::styling::{AnsiColor, Styles}};

const CARGO_STYLE: Styles = Styles::styled()
    .header(AnsiColor::Green.on_default().bold())
    .usage(AnsiColor::Green.on_default().bold())
    .literal(AnsiColor::Cyan.on_default().bold())
    .placeholder(AnsiColor::Cyan.on_default());

/// Open the nearest Cargo.toml with the editor defined in $EDITOR.
///
/// This command works from any subdirectory of a Cargo workspace. It
/// internally runs `cargo locate-project --message-format plain` to
/// discover the manifest file, then launches the editor.
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None, styles = CARGO_STYLE)]
struct Args {
    /// Override the $EDITOR environment variable for this invocation.
    #[arg(short, long)]
    editor: Option<String>,
}

fn main() -> anyhow::Result<()> {
    let raw_args: Vec<String> = env::args().collect();
    // If the program is being run directly (e.g. `cargo-edit-toml`),
    // there is no extra token, so we just pass everything unchanged.
    let args_to_parse = if raw_args.get(1).map(|s| s == "edit-toml").unwrap_or(false) {
        // Drop the spurious "edit-toml"
        raw_args.iter().enumerate()
            .filter_map(|(i, v)| if i != 1 { Some(v.clone()) } else { None })
            .collect::<Vec<_>>()
    } else {
        raw_args.clone()
    };
    // Parse CLI arguments
    let args = Args::parse_from(args_to_parse);

    // Determine which editor to launch
    let editor = match args.editor {
        Some(e) => e,
        None => env::var("EDITOR").unwrap_or_else(|_| {
            // Fallback to a very common editor if $EDITOR is unset.
            // Users can still override via `--editor`.
            "vi".to_string()
        }),
    };

    // Locate the Cargo.toml using `cargo locate-project`
    let proj_loc = Command::new("cargo")
        .args(&["locate-project", "--message-format", "plain"])
        .stdout(Stdio::piped())
        .stderr(Stdio::inherit())
        .output()?;

    if !proj_loc.status.success() {
        anyhow::bail!(
            "`cargo locate-project` failed – are you inside a Cargo project?"
        );
    }

    // The command prints the absolute path followed by a newline.
    let manifest_path = String::from_utf8(proj_loc.stdout)?
        .trim_end_matches('\n')
        .to_owned();

    // Launch the editor
    let status = Command::new(editor)
        .arg(&manifest_path)
        .status()
        .map_err(|e| {
            anyhow::anyhow!(
                "Failed to spawn the editor. Make sure the editor binary is in your PATH. ({})",
                e
            )
        })?;

    if !status.success() {
        anyhow::bail!("Editor exited with a non‑zero status");
    }

    Ok(())
}