fission-command-ui 0.14.1

Terminal UI application for the fission command
Documentation
use super::*;

pub(super) fn copy_or_move_selected_file(
    selected: &Path,
    workspace: &Path,
    default_name: &str,
    move_file: bool,
) -> Result<PathBuf> {
    fs::create_dir_all(workspace)
        .with_context(|| format!("failed to create {}", workspace.display()))?;
    set_private_dir_permissions(workspace)?;
    let dest = workspace.join(default_name);
    if move_file {
        fs::rename(selected, &dest).with_context(|| {
            format!(
                "failed to move {} to {}",
                selected.display(),
                dest.display()
            )
        })?;
    } else {
        fs::copy(selected, &dest).with_context(|| {
            format!(
                "failed to copy {} to {}",
                selected.display(),
                dest.display()
            )
        })?;
    }
    set_private_file_permissions(&dest)?;
    Ok(dest)
}

pub(super) fn path_is_inside_project(path: &Path, project_dir: &Path) -> bool {
    let Ok(project) = project_dir.canonicalize() else {
        return false;
    };
    let Ok(path) = path.canonicalize() else {
        return false;
    };
    path.starts_with(project)
}

pub(super) fn read_env_entries(path: &Path) -> Result<std::collections::BTreeMap<String, String>> {
    let mut entries = std::collections::BTreeMap::new();
    if !path.exists() {
        return Ok(entries);
    }
    let text = fs::read_to_string(path)?;
    for line in text.lines() {
        let line = line.trim();
        if line.is_empty() || line.starts_with('#') {
            continue;
        }
        let line = line.strip_prefix("export ").unwrap_or(line);
        if let Some((key, value)) = line.split_once('=') {
            entries.insert(key.trim().to_string(), unquote_env_value(value.trim()));
        }
    }
    Ok(entries)
}

pub(super) fn upsert_env(path: &Path, key: &str, value: &str) -> Result<()> {
    let mut entries = read_env_entries(path)?;
    entries.insert(key.to_string(), value.to_string());
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
        set_private_dir_permissions(parent)?;
    }
    let mut out = String::new();
    out.push_str("# Fission local release environment. Do not commit this file.\n");
    out.push_str("# Generated by the publish cockpit.\n");
    for (key, value) in entries {
        out.push_str("export ");
        out.push_str(&key);
        out.push('=');
        out.push_str(&quote_env_value(&value));
        out.push('\n');
    }
    fs::write(path, out)?;
    set_private_file_permissions(path)?;
    Ok(())
}

pub(super) fn quote_env_value(value: &str) -> String {
    format!(
        "\"{}\"",
        value
            .replace('\\', "\\\\")
            .replace('"', "\\\"")
            .replace('$', "\\$")
    )
}

pub(super) fn unquote_env_value(value: &str) -> String {
    let trimmed = value.trim();
    if trimmed.len() >= 2 && trimmed.starts_with('"') && trimmed.ends_with('"') {
        let inner = &trimmed[1..trimmed.len() - 1];
        inner
            .replace("\\\"", "\"")
            .replace("\\$", "$")
            .replace("\\\\", "\\")
    } else {
        trimmed.to_string()
    }
}

pub(super) fn sanitize_workspace_name(value: &str) -> String {
    let out = value
        .chars()
        .map(|ch| match ch {
            'A'..='Z' | 'a'..='z' | '0'..='9' | '-' | '_' | '.' => ch.to_ascii_lowercase(),
            _ => '-',
        })
        .collect::<String>()
        .trim_matches(['-', '.', '_'])
        .to_string();
    if out.is_empty() {
        "app".to_string()
    } else {
        out
    }
}

#[cfg(unix)]
pub(super) fn set_private_dir_permissions(path: &Path) -> Result<()> {
    use std::os::unix::fs::PermissionsExt;
    fs::set_permissions(path, fs::Permissions::from_mode(0o700))?;
    Ok(())
}

#[cfg(not(unix))]
pub(super) fn set_private_dir_permissions(_path: &Path) -> Result<()> {
    Ok(())
}

#[cfg(unix)]
pub(super) fn set_private_file_permissions(path: &Path) -> Result<()> {
    use std::os::unix::fs::PermissionsExt;
    fs::set_permissions(path, fs::Permissions::from_mode(0o600))?;
    Ok(())
}

#[cfg(not(unix))]
pub(super) fn set_private_file_permissions(_path: &Path) -> Result<()> {
    Ok(())
}