diff --git a/Cargo.lock b/Cargo.lock index a53ea94..dd3e474 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,7 +46,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -56,7 +56,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -115,6 +115,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -175,6 +185,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "log" version = "0.4.20" @@ -236,6 +252,7 @@ name = "saul" version = "0.1.0" dependencies = [ "clap", + "colored", "ignore", ] @@ -329,13 +346,37 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -344,51 +385,93 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index eb70491..c950171 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,4 +8,5 @@ license = "AGPL-3.0-or-later" [dependencies] clap = { version = "4.5.0", features = ["derive"] } +colored = "2.1.0" ignore = "0.4.22" diff --git a/src/log.rs b/src/log.rs new file mode 100644 index 0000000..6a5c7f6 --- /dev/null +++ b/src/log.rs @@ -0,0 +1,119 @@ +// Copyright (c) 2024 Marceline Cramer +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This file is part of Saul. +// +// Saul is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// Saul is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Saul. If not, see . + +use std::{cell::RefCell, collections::HashMap}; + +use colored::*; + +#[derive(Clone, Copy, Debug)] +pub enum LintLevel { + Deny, + Warning, + Ignore, +} + +pub fn init_lints() { + unsafe { + LINTS = Some(HashMap::new()); + } +} + +pub fn lint(name: &str, body: &str) { + let lints = unsafe { LINTS.as_mut().expect("lints uninitialized") }; + let level = lints.get(name).copied().unwrap_or(LintLevel::Warning); + + let prefix = match level { + LintLevel::Deny => format!("deny({name})").red().bold(), + LintLevel::Warning => format!("warning({name})").yellow().bold(), + LintLevel::Ignore => return, + }; + + let msg = format!("{prefix}: {body}"); + log(msg.as_str()); +} + +static mut LINTS: Option> = None; + +pub fn log(body: &str) { + with_logger(|l| l.message(body)); +} + +pub fn with_context(ctx: &str, body: impl FnOnce()) { + with_logger(|l| l.push_context(ctx.to_string())); + body(); + with_logger(|l| l.pop_context()); +} + +static mut LOGGER: RefCell = RefCell::new(Logger::new()); + +fn with_logger(f: impl Fn(&mut Logger)) { + unsafe { f(&mut LOGGER.borrow_mut()) } +} + +struct Context { + label: String, + rendered: bool, +} + +struct Logger { + contexts: Vec, +} + +impl Logger { + const fn new() -> Self { + Self { contexts: vec![] } + } + + fn message(&mut self, body: &str) { + let indent = |lvl| std::iter::repeat(" ").take(lvl).collect::(); + + let mut rendered_contexts = Vec::with_capacity(self.contexts.len()); + for (lvl, ctx) in self.contexts.iter_mut().enumerate().rev() { + if ctx.rendered { + break; + } + + let label = ctx.label.underline(); + + let out = format!("{}{}", indent(lvl), label); + rendered_contexts.push(out); + ctx.rendered = true; + } + + while let Some(out) = rendered_contexts.pop() { + println!("{out}"); + } + + let lvl = self.contexts.len(); + let indent = indent(lvl); + for line in body.lines() { + println!("{}{}", indent, line); + } + } + + fn push_context(&mut self, label: String) { + self.contexts.push(Context { + label, + rendered: false, + }); + } + + fn pop_context(&mut self) { + self.contexts.pop(); + } +} diff --git a/src/main.rs b/src/main.rs index d02eaf6..da45a35 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,6 +21,7 @@ use std::{fs::File, io::BufReader, path::PathBuf}; use clap::Parser; use ignore::Walk; +mod log; mod parse; #[derive(Debug, Parser)] @@ -31,6 +32,7 @@ struct Args { fn main() { let mut args = Args::parse(); + log::init_lints(); if args.input.is_empty() { args.input = vec![".".into()]; @@ -55,20 +57,26 @@ fn main() { } let path = entry.path(); - let f = File::open(path).unwrap(); - let mut read = BufReader::new(f); + let filename = path.to_string_lossy().to_owned(); - let lines = match parse::Language::RUST.read_header(&mut read) { - Ok(lines) => lines, - Err(err) => { - eprintln!("error parsing {:?}: {:?}", path, err); - continue; + log::with_context(&filename, || { + let f = File::open(path).unwrap(); + let mut read = BufReader::new(f); + + let lines = match parse::Language::RUST.read_header(&mut read) { + Ok(lines) => lines, + Err(err) => { + log::log(&format!("error parsing {:?}: {:?}", path, err)); + return; + } + }; + + let header = parse::Header::parse(lines).unwrap(); + + if header.spdx.is_none() { + log::lint("missing_spdx", "header is missing SPDX license identifier"); } - }; - - let header = parse::Header::parse(lines).unwrap(); - - println!("{:?}: {:#?}", path, header); + }); } } }