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

//! Module related to the properties of EditorConfig.

use core::result::Result;
use std::path::Path;

use anyhow::Result as anyResult;
use charset::CharsetHandler;
use end_of_line::EndOfLineHandler;
use indentation::IndentationHandler;
use insert_final_newline::InsertFinalNewLineHandler;
use log::info;
use spelling_language::SpellingLanguageHandler;
use trim_trailing_whitespace::TrimTrailingWhitespaceHandler;

use crate::cli::EditorConfigArgs;

mod charset;

mod end_of_line;

mod indentation;

mod insert_final_newline;

mod spelling_language;

mod trim_trailing_whitespace;

pub mod errors;

pub mod status_types;

/// Number of EditorConfig properties
const PROPERTIES_COUNT: usize = ec4rs::property::STANDARD_KEYS.len();

pub fn check_file(path: &Path, editorconfig_args: &EditorConfigArgs) -> anyResult<()> {
    info!("check {}", path.display());
    for handler in build_file_handlers(path, editorconfig_args)? {
        handler.check(path)?
    }
    Ok(())
}

pub fn fix_file(path: &Path, editorconfig_args: &EditorConfigArgs) -> anyResult<()> {
    info!("fix {}", path.display());
    for handler in build_file_handlers(path, editorconfig_args)? {
        handler.fix(path)?
    }
    Ok(())
}

pub fn check_properties(
    properties: &ec4rs::Properties,
    path: &Path,
    section: usize,
    handlers: &[BoxedPropConfigHandler],
) -> anyResult<()> {
    info!(
        "check section #{} in EditorConfig {}",
        section + 1,
        path.display()
    );
    for handler in handlers {
        handler.check(properties)?;
    }
    Ok(())
}

/// Builds the array of all applicable [`PropertyHandler`] for the given file path.
fn build_file_handlers(
    file_path: &Path,
    editorconfig_args: &EditorConfigArgs,
) -> Result<Box<[BoxedPropHandler]>, ec4rs::Error> {
    let handler_count = 5; // maximal number of handlers
    let properties = ec4rs::properties_of(file_path)?;
    let mut handlers: Vec<Option<BoxedPropHandler>> = Vec::with_capacity(handler_count);

    if editorconfig_args.charset {
        handlers.push(CharsetHandler::build(&properties).map(|h| Box::new(h) as BoxedPropHandler));
    }
    if editorconfig_args.end_of_line {
        handlers
            .push(EndOfLineHandler::build(&properties).map(|h| Box::new(h) as BoxedPropHandler));
    }
    if editorconfig_args.trim_trailing_whitespace {
        handlers.push(
            TrimTrailingWhitespaceHandler::build(&properties)
                .map(|h| Box::new(h) as BoxedPropHandler),
        );
    }
    if editorconfig_args.insert_final_newline {
        handlers.push(
            InsertFinalNewLineHandler::build(&properties).map(|h| Box::new(h) as BoxedPropHandler),
        );
    }
    if editorconfig_args.indentation_handling {
        handlers
            .push(IndentationHandler::build(&properties).map(|h| Box::new(h) as BoxedPropHandler));
    }
    debug_assert!(handlers.len() <= handler_count, "handler count is to small");
    Ok(handlers.into_iter()
        .flatten() // filter out "None" entries
        .collect::<Vec<BoxedPropHandler>>().into_boxed_slice())
}

type BoxedPropHandler = Box<dyn PropertyHandler>;

/// Trait for handlers for a EditorConfig property on a single file.
trait PropertyHandler {
    /// Performs the handler's check on the given file.
    /// If a EditorConfig Property is violated,
    /// it returns a [`errors::CheckError`] inside a [`anyhow::Error`].
    fn check(&self, file_path: &Path) -> anyResult<()>;

    /// Performs the handler's fix on the given file.
    fn fix(&self, file_path: &Path) -> anyResult<()>;
}

/// Builds the vector of all applicable [`PropertyConfigHandler`]
/// for the enabled properties in the given `editorconfig_args`.
pub(crate) fn build_config_handlers(
    editorconfig_args: &EditorConfigArgs,
) -> Vec<BoxedPropConfigHandler> {
    let handler_count = 1; // maximal number of handlers
    let mut handlers: Vec<BoxedPropConfigHandler> = Vec::with_capacity(handler_count);

    if editorconfig_args.spelling_language {
        handlers.push(Box::new(SpellingLanguageHandler {}) as BoxedPropConfigHandler);
    }

    debug_assert!(handlers.len() <= handler_count, "handler count is to small");
    handlers
}

type BoxedPropConfigHandler = Box<dyn PropertyConfigHandler>;

/// Trait for handlers for a EditorConfig property on properties instance.
/// These only validate the properties themselves without considering any file.
pub(crate) trait PropertyConfigHandler {
    /// Performs the handler's check on the properties.
    /// If a EditorConfig Property has an error,
    /// it returns a [`errors::CheckConfigError`] inside a [`anyhow::Error`].
    fn check(&self, properties: &ec4rs::Properties) -> anyResult<()>;
}