use crate::{ceprintlns, ReadFile};
use std::path::PathBuf;
use sysexits::Result;
#[allow(clippy::struct_excessive_bools)]
#[derive(clap::Parser, Clone)]
pub struct Complain {
files: Vec<PathBuf>,
#[arg(long)]
ignore_carriage_return_line_feeds: bool,
#[arg(long)]
ignore_line_width_issues: bool,
#[arg(long)]
ignore_missing_final_line_feed: bool,
#[arg(long)]
ignore_mixed_indentation: bool,
#[arg(long)]
ignore_tabs_within_lines: bool,
#[arg(long)]
ignore_trailing_white_space_characters: bool,
#[arg(long)]
ignore_wrong_indentation: bool,
#[arg(default_value = "spaces", long, short)]
indent_by: IndentationUnit,
#[arg(
default_value = "80",
long,
short,
visible_aliases = ["length", "line", "width"]
)]
line_width: usize,
}
impl Complain {
pub fn indent_by(&mut self, i: IndentationUnit) {
self.indent_by = i;
}
pub fn main(&self) -> Result<()> {
self.wrap().main()
}
#[must_use]
pub fn new(files: Vec<PathBuf>) -> Self {
Self {
files,
ignore_carriage_return_line_feeds: false,
ignore_line_width_issues: false,
ignore_missing_final_line_feed: false,
ignore_mixed_indentation: false,
ignore_tabs_within_lines: false,
ignore_trailing_white_space_characters: false,
ignore_wrong_indentation: false,
indent_by: IndentationUnit::Spaces,
line_width: 80,
}
}
pub fn process(&self) -> Result<usize> {
self.wrap().process()
}
fn wrap(&self) -> Logic {
Logic {
cli: self.clone(),
data: String::new(),
errors: 0,
total_errors: 0,
}
}
}
#[derive(Clone, Copy, Default, Eq, PartialEq)]
pub enum IndentationUnit {
#[default]
Spaces,
Tabs,
}
crate::enum_trait!(IndentationUnit {
Spaces <-> "spaces",
Tabs <-> "tabs"
});
struct Logic {
cli: Complain,
data: String,
errors: usize,
total_errors: usize,
}
impl Logic {
fn ac_0001(&mut self) -> Result<()> {
if !self.data.ends_with('\n') {
self.errors += 1;
ceprintlns!(
"AC-0001"!Green,
"File not terminated by line feed."
);
}
Ok(())
}
fn ac_0002(&mut self) -> Result<()> {
let mut line = 1;
for l in self.data.split_inclusive('\n') {
if l.ends_with("\r\n") {
self.errors += 1;
ceprintlns!(
"AC-0002"!Yellow,
"CRLF in line {line}."
);
}
line += 1;
}
Ok(())
}
fn ac_0003(&mut self) -> Result<()> {
let mut line = 1;
for l in self.data.lines() {
let c = l.chars().count();
if c > self.cli.line_width {
self.errors += 1;
ceprintlns!(
"AC-0003"!Red,
"Line {line} is {} character(s) too long.",
c - self.cli.line_width
);
}
line += 1;
}
Ok(())
}
fn ac_0004(&mut self) -> Result<()> {
let mut line = 1;
for l in self.data.lines() {
if l.ends_with(char::is_whitespace) {
self.errors += 1;
ceprintlns!(
"AC-0004"!Green,
"TWS in line {line}."
);
}
line += 1;
}
Ok(())
}
fn ac_0005(&mut self) -> Result<()> {
let mut line = 1;
let trigger = match self.cli.indent_by {
IndentationUnit::Spaces => '\t',
IndentationUnit::Tabs => ' ',
};
for l in self.data.lines() {
if l.starts_with(trigger) {
self.errors += 1;
ceprintlns!(
"AC-0005"!Green,
"Line {line} indented by {}.",
if trigger == '\t' { "tabs" } else { "spaces" }
);
}
line += 1;
}
Ok(())
}
fn ac_0006(&mut self) -> Result<()> {
let mut line = 1;
for l in self.data.lines() {
if l.split_once(|c| !char::is_whitespace(c)).is_some_and(
|(indentation, _)| {
indentation.contains('\t') && indentation.contains(' ')
},
) {
self.errors += 1;
ceprintlns!(
"AC-0006"!Yellow,
"Line {line} is indented by both spaces and tabs."
);
}
line += 1;
}
Ok(())
}
fn ac_0007(&mut self) -> Result<()> {
let mut line = 1;
for l in self.data.lines() {
if l.trim().contains('\t') {
self.errors += 1;
ceprintlns!(
"AC-0007"!Yellow,
"Tabs within line {line}."
);
}
line += 1;
}
Ok(())
}
fn complain(&mut self, f: &PathBuf) -> Result<()> {
self.data = f.read()?;
if !self.cli.ignore_missing_final_line_feed {
self.ac_0001()?;
}
if !self.cli.ignore_carriage_return_line_feeds {
self.ac_0002()?;
}
if !self.cli.ignore_line_width_issues {
self.ac_0003()?;
}
if !self.cli.ignore_trailing_white_space_characters {
self.ac_0004()?;
}
if !self.cli.ignore_wrong_indentation {
self.ac_0005()?;
}
if !self.cli.ignore_mixed_indentation {
self.ac_0006()?;
}
if !self.cli.ignore_tabs_within_lines {
self.ac_0007()?;
}
ceprintlns!("ˇ;{\"};ˇ"!Blue, "{} {}", self.errors, f.display());
self.total_errors += self.errors;
self.errors = 0;
Ok(())
}
fn main(&mut self) -> Result<()> {
if self.process()? == 0 {
Ok(())
} else {
Err(sysexits::ExitCode::DataErr)
}
}
fn process(&mut self) -> Result<usize> {
for f in self.cli.files.clone() {
self.complain(&f)?;
}
Ok(self.total_errors)
}
}