prek 0.5.5

A fast Git hook manager written in Rust, designed as a drop-in alternative to pre-commit, reimagined.
use std::fmt::Write as _;
use std::io::Write;
use std::path::{Path, PathBuf};

use anyhow::Result;
use owo_colors::OwoColorize;
use prek_consts::{PRE_COMMIT_CONFIG_YAML, PREK_TOML};

use crate::cli::{ExitStatus, SampleConfigFormat, SampleConfigTarget};
use crate::fs::Simplified;
use crate::printer::Printer;

static SAMPLE_CONFIG_YAML: &str = indoc::indoc! {"
# See https://prek.j178.dev for more information.
# yaml-language-server: $schema=https://www.schemastore.org/prek.json
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v6.0.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-added-large-files
"};

static SAMPLE_CONFIG_TOML: &str = indoc::indoc! {r#"
# Configuration file for `prek`, a git hook framework written in Rust.
# See https://prek.j178.dev for more information.
#:schema https://www.schemastore.org/prek.json

[[repos]]
repo = "builtin"
hooks = [
    { id = "trailing-whitespace" },
    { id = "end-of-file-fixer" },
    { id = "check-added-large-files" },
]
"#};

pub(crate) fn sample_config(
    target: SampleConfigTarget,
    format: Option<SampleConfigFormat>,
    printer: Printer,
) -> Result<ExitStatus> {
    let (path, format) = match (target, format) {
        (SampleConfigTarget::Path(path), Some(format)) => (Some(path), format),
        (SampleConfigTarget::Path(path), None) => match path.extension() {
            Some(ext) if ext.eq_ignore_ascii_case("toml") => (Some(path), SampleConfigFormat::Toml),
            _ => (Some(path), SampleConfigFormat::Yaml),
        },
        (SampleConfigTarget::DefaultFile, Some(format)) => match format {
            SampleConfigFormat::Toml => (Some(PathBuf::from(PREK_TOML)), format),
            SampleConfigFormat::Yaml => (Some(PathBuf::from(PRE_COMMIT_CONFIG_YAML)), format),
        },
        (SampleConfigTarget::DefaultFile, None) => (
            Some(PathBuf::from(PRE_COMMIT_CONFIG_YAML)),
            SampleConfigFormat::Yaml,
        ),
        (SampleConfigTarget::Stdout, Some(format)) => (None, format),
        (SampleConfigTarget::Stdout, None) => (None, SampleConfigFormat::Yaml),
    };

    if let Some(path) = path {
        write_sample_config(&path, format)?;

        writeln!(
            printer.stdout(),
            "Written to `{}`",
            path.simplified_display().cyan()
        )?;

        return Ok(ExitStatus::Success);
    }

    // TODO: default to prek.toml in the future?
    match format {
        SampleConfigFormat::Yaml => {
            write!(printer.stdout_important(), "{SAMPLE_CONFIG_YAML}")?;
        }
        SampleConfigFormat::Toml => {
            write!(printer.stdout_important(), "{SAMPLE_CONFIG_TOML}")?;
        }
    }
    Ok(ExitStatus::Success)
}

pub(crate) fn write_sample_config(path: &Path, format: SampleConfigFormat) -> Result<()> {
    fs_err::create_dir_all(path.parent().unwrap_or(Path::new(".")))?;
    let mut file = match fs_err::OpenOptions::new()
        .write(true)
        .create_new(true)
        .open(path)
    {
        Ok(file) => file,
        Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => {
            anyhow::bail!("File `{}` already exists", path.simplified_display().cyan());
        }
        Err(err) => return Err(err.into()),
    };

    match format {
        SampleConfigFormat::Yaml => write!(file, "{SAMPLE_CONFIG_YAML}")?,
        SampleConfigFormat::Toml => write!(file, "{SAMPLE_CONFIG_TOML}")?,
    }

    Ok(())
}