logloom 1.0.0

Parse, validate, and export changelogs with a CLI and Rust library
Documentation
use std::fs;
use std::io::{self, Write};
use std::path::Path;

use tempfile::NamedTempFile;

use super::CliError;
use crate::diagnostics::Reporter;

pub fn write_file(
    path: &Path,
    input: &Path,
    bytes: &[u8],
    reporter: &Reporter,
) -> Result<(), CliError> {
    if input != Path::new("-")
        && same_path(input, path).map_err(|error| output_error(path, &error, reporter))?
    {
        return Err(CliError::new(
            2,
            reporter.error(
                "output-path",
                "input and output refer to the same file",
                "Choose a different --output-file path to keep your changelog intact.",
            ),
        ));
    }
    persist(path, bytes).map_err(|error| output_error(path, &error, reporter))
}

fn same_path(input: &Path, output: &Path) -> io::Result<bool> {
    let output = match fs::canonicalize(output) {
        Ok(path) => path,
        Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(false),
        Err(error) => return Err(error),
    };
    Ok(fs::canonicalize(input)? == output)
}

fn persist(path: &Path, bytes: &[u8]) -> io::Result<()> {
    let parent = path
        .parent()
        .filter(|parent| !parent.as_os_str().is_empty())
        .unwrap_or_else(|| Path::new("."));
    let mut temporary = NamedTempFile::new_in(parent)?;
    temporary.write_all(bytes)?;
    temporary.flush()?;
    temporary.persist(path).map_err(|error| error.error)?;
    Ok(())
}

fn output_error(path: &Path, error: &io::Error, reporter: &Reporter) -> CliError {
    CliError::new(
        3,
        reporter.error(
            "output-io",
            &format!("cannot write {}: {error}", path.display()),
            match error.kind() {
                io::ErrorKind::NotFound => {
                    "Create the parent directory first, or choose an existing directory."
                }
                io::ErrorKind::IsADirectory => {
                    "Choose a file path instead of a directory, e.g. --output-file changelog.json."
                }
                io::ErrorKind::PermissionDenied => "Choose a writable output file and directory.",
                _ => {
                    "Check that the parent directory is writable and that disk space is available."
                }
            },
        ),
    )
}