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);
+ });
}
}
}