saul/src/log.rs

120 lines
3.1 KiB
Rust

// 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 <https://www.gnu.org/licenses/>.
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<HashMap<String, LintLevel>> = 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<Logger> = 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<Context>,
}
impl Logger {
const fn new() -> Self {
Self { contexts: vec![] }
}
fn message(&mut self, body: &str) {
let indent = |lvl| std::iter::repeat(" ").take(lvl).collect::<String>();
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();
}
}