ecformat 0.2.0

command line tool to keep files correct in respect of your EditorConfig
Documentation
// SPDX-FileCopyrightText: Contributors to ecformat project <https://codeberg.org/BaumiCoder/ecformat>
//
// SPDX-License-Identifier: BlueOak-1.0.0

use anyhow::Result;
use log::error;
use snafu::ensure;

pub mod cli;

mod editorconfig;

mod files;

#[cfg(test)]
pub(crate) mod test_utils;

use cli::{CommandArgs, LibCommands};
use editorconfig::status_types::{ConsideredFiles, EditorConfigFiles, StatusInfo, StatusTexts};

pub use crate::editorconfig::errors;
pub use crate::editorconfig::status_types;

/// Execute one of the CLI lib commands.
/// In case of [`LibCommands::Status`] the status info is printed to stdout,
/// depending on the [`verbosity`](CommandArgs::verbosity).
/// If you want to use [`status_types::StatusInfo`] directly, call [`status`].
/// (The other CLI commands are not supported by the lib crate.)
/// See also [`check`], [`fix`] and [`status`].
pub fn execute(cmd: &LibCommands) -> Result<()> {
    match cmd {
        LibCommands::Check(args) => check(args),
        LibCommands::Fix(args) => fix(args),
        LibCommands::Status(args) => status(args).map(|s| {
            if let Some(log_level) = args.verbosity().log_level() {
                // Only print something if not silent
                if log_level < log::Level::Warn {
                    s.print_short_text();
                } else {
                    // Print long text if level is Warn or higher
                    s.print_long_text();
                }
            }
        }),
    }
}

/// Execute the check command.
/// The error in the Result is a [`errors::CheckErrorList`]
/// if the check found violations against your EditorConfig.
/// Use a downcast method to check if it is the case and access the errors list.
pub fn check(args: &CommandArgs) -> Result<()> {
    let mut errors = Vec::new();
    let targets = &args.targets()?;

    // Process all not ignored files
    for entry in files::get_target_files(targets, &args.ignore_args) {
        let path = entry?;
        if let Err(e) = editorconfig::check_file(&path, &args.editorconfig_args) {
            let check_error = errors::CheckSnafu {
                path,
                error_type: e.downcast::<editorconfig::errors::CheckErrorType>()?,
            }
            .build();

            error!("{check_error}");
            errors.push(errors::EditorConfigError::FileError(check_error));
        }
    }
    // Process all EditorConfig files
    let config_handlers = editorconfig::build_config_handlers(&args.editorconfig_args);
    if !config_handlers.is_empty() {
        for entry in files::get_editorconfig_files(targets, &args.ignore_args) {
            let mut config = entry?;
            let mut section: usize = 0;
            while !config.reader.has_more() {
                let properties = config.reader.read_section()?.into_props();
                if let Err(e) = editorconfig::check_properties(
                    &properties,
                    &config.path,
                    section,
                    &config_handlers,
                ) {
                    let check_config_error = errors::CheckConfigSnafu {
                        path: &config.path,
                        section,
                        error_type: e.downcast::<editorconfig::errors::CheckConfigErrorType>()?,
                    }
                    .build();

                    error!("{check_config_error}");
                    errors.push(errors::EditorConfigError::ConfigError(check_config_error));
                }
                section += 1;
            }
        }
    }

    ensure!(
        errors.is_empty(),
        editorconfig::errors::CheckErrorListSnafu { errors }
    );
    Ok(())
}

/// Execute the fix command.
pub fn fix(args: &CommandArgs) -> Result<()> {
    for entry in files::get_target_files(&args.targets()?, &args.ignore_args) {
        editorconfig::fix_file(&entry?, &args.editorconfig_args)?
    }

    Ok(())
}

/// Execute the status command.
/// The error in the Result is a [`errors::NoEditorConfigFound`]
/// if no EditorConfig for the given targets can be found.
pub fn status<'a>(args: &'a CommandArgs) -> Result<StatusInfo<'a>> {
    let targets = &args.targets()?;
    let configs = files::get_editorconfig_files(targets, &args.ignore_args);
    let editorconfig_files = EditorConfigFiles::build(configs)?;
    let considered_files = ConsideredFiles::build(
        files::get_target_files(targets, &args.ignore_args),
        &args.editorconfig_args,
    )?;

    Ok(StatusInfo {
        editorconfig_files,
        considered_files,
    })
}