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 for the `insert_final_newline` property of EditorConfig.

use ec4rs::property::{self, FinalNewline};
use line_ending::LineEnding;
use snafu::ensure;

use crate::{errors, files};

use super::{PropertyHandler, charset, end_of_line};

/// Handles the `insert_final_newline` property for a single file.
pub struct InsertFinalNewLineHandler {
    charset: property::Charset,
    line_ending: LineEnding,
}

impl PropertyHandler for InsertFinalNewLineHandler {
    fn check(&self, file_path: &std::path::Path) -> anyhow::Result<()> {
        let content = files::read_file(file_path, &self.charset)?;
        let needs_final_newline = self.needs_final_newline(&content);

        ensure!(!needs_final_newline, errors::InsertFinalNewlineSnafu {});
        Ok(())
    }

    fn fix(&self, file_path: &std::path::Path) -> anyhow::Result<()> {
        let mut content = files::read_file(file_path, &self.charset)?;
        if self.add_final_newline(&mut content) {
            files::overwrite_file(file_path, &self.charset, &content)?;
        }
        Ok(())
    }
}

impl InsertFinalNewLineHandler {
    /// Creates a [`InsertFinalNewLineHandler`] for the given properties,
    /// if a handler is necessary for these properties.
    pub fn build(properties: &ec4rs::Properties) -> Option<InsertFinalNewLineHandler> {
        match properties.get::<property::FinalNewline>() {
            Ok(FinalNewline::Value(true)) => Some(InsertFinalNewLineHandler {
                charset: charset::get_charset(properties),
                line_ending: end_of_line::get_line_ending(properties),
            }),
            // insert_final_newline is false or property is not set
            Ok(FinalNewline::Value(false)) | Err(_) => None,
        }
    }

    /// Returns if the given content has the need for a final newline.
    fn needs_final_newline(&self, content: &str) -> bool {
        // Not necessary in empty files (EditorConfig specification 0.17.2)
        !content.is_empty() && !content.ends_with(self.line_ending.as_str())
    }

    /// Adds a final newline, if necessary.
    /// Returns true if a final newline was added (i.e., content was changed).
    fn add_final_newline(&self, content: &mut String) -> bool {
        let needs_final_newline = self.needs_final_newline(content);
        if needs_final_newline {
            content.push_str(self.line_ending.as_str());
        }
        needs_final_newline
    }
}

#[cfg(test)]
mod tests;