vscli 1.3.3

A CLI tool to launch vscode projects, which supports devcontainers.
use super::{info, list, stop};
use crate::opts::ContainerAction;
use crate::ui;
use crate::workspace::{self, DevContainer, Workspace};
use color_eyre::eyre::{Result, bail};
use log::info;
use std::path::{Path, PathBuf};

fn run_ui(editor: &str) -> Result<()> {
    let containers = list(false)?;
    if containers.is_empty() {
        println!("no running devcontainers");
        return Ok(());
    }

    let mut stop_cb = |item: &ui::ContainerItem| {
        if let Err(error) = stop(&item.0.id) {
            log::warn!("Failed to stop container {}: {error}", item.0.short_id);
        }
    };
    let selected = ui::pick_container(containers, ui::PickerOpts::default(), Some(&mut stop_cb))?;
    let Some(container) = selected else {
        return Ok(());
    };

    info!("Reopening container {} ...", container.short_id);
    let container_info = info(&container.id)?;
    let local_folder = workspace::resolve_local_path(&container_info.local_folder);
    let project_path = Path::new(&local_folder);
    if !project_path.exists() {
        bail!("Project path does not exist: {}", project_path.display());
    }

    let workspace = Workspace::from_path(project_path)?;
    let config_file = workspace::resolve_local_path(&container_info.config_file);
    let config_path = PathBuf::from(config_file);
    if config_path.exists() {
        let dev_container = DevContainer::from_config(&config_path, &workspace.name)?;
        workspace.open(vec![], false, &dev_container, editor, None)
    } else {
        workspace.open_classic(vec![], false, editor)
    }
}

fn print_containers(all: bool) -> Result<()> {
    let containers = list(all)?;
    if containers.is_empty() {
        println!("no {}devcontainers", if all { "" } else { "running " });
        return Ok(());
    }

    let id_width = 12;
    let status_width = containers
        .iter()
        .map(|container| container.status.len())
        .max()
        .unwrap_or(6);
    let image_width = containers
        .iter()
        .map(|container| container.image.len())
        .max()
        .unwrap_or(5);
    println!(
        "{:<id_width$}  {:<status_width$}  {:<image_width$}  PROJECT PATH",
        "CONTAINER ID", "STATUS", "IMAGE"
    );
    for container in containers {
        println!(
            "{:<id_width$}  {:<status_width$}  {:<image_width$}  {}",
            container.short_id, container.status, container.image, container.local_folder
        );
    }
    Ok(())
}

fn print_container_info(id: &str) -> Result<()> {
    let container = info(id)?;
    let created = chrono::DateTime::parse_from_rfc3339(&container.created)
        .map(|date| {
            chrono::DateTime::<chrono::Local>::from(date)
                .format("%Y-%m-%d %H:%M:%S")
                .to_string()
        })
        .unwrap_or(container.created);
    println!("Container:    {}", container.id);
    println!("Name:         {}", container.name);
    println!("Image:        {}", container.image);
    println!("Status:       {}", container.status);
    println!("Created:      {created}");
    println!("Project:      {}", container.local_folder);
    println!("Config:       {}", container.config_file);
    println!("Ports:        {}", container.ports);
    if container.mounts.is_empty() {
        println!("Mounts:       none");
    } else {
        for (index, mount) in container.mounts.iter().enumerate() {
            if index == 0 {
                println!("Mounts:       {mount}");
            } else {
                println!("              {mount}");
            }
        }
    }
    Ok(())
}

pub fn run_command(action: ContainerAction, editor: &str) -> Result<()> {
    match action {
        ContainerAction::Ui => run_ui(editor)?,
        ContainerAction::List { all } => print_containers(all)?,
        ContainerAction::Info { id } => print_container_info(&id)?,
        ContainerAction::Stop { id } => {
            stop(&id)?;
            info!("Stopped container {id}");
        }
    }
    Ok(())
}