Skip to content

Commit

Permalink
Add logger trait (#93)
Browse files Browse the repository at this point in the history
Introduced a new `Logger` trait which can be used to access all log events
emitted during compilation. Currently these only include messages emitted by the
`@debug` and `@warn` statements.

Changes were implemented in a backwards-compatible manner, but the current
`Options::quiet` method has been marked as deprecated, as its behavior can be
achieved using the `NullLogger` structure. The default logger used is
`StdLogger` which writes all log events to standard error. This reflect the
default behavior prior to introduction of `Logger`.

With these new changes, it is also now possible to properly test the `@debug`
and `@warn` statements.
  • Loading branch information
cryocz authored May 19, 2024
1 parent 8d3258d commit a1ca700
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 35 deletions.
16 changes: 3 additions & 13 deletions crates/compiler/src/evaluate/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1042,14 +1042,10 @@ impl<'a> Visitor<'a> {
}

let message = self.visit_expr(debug_rule.value)?;
let message = message.inspect(debug_rule.span)?;

let loc = self.map.look_up_span(debug_rule.span);
eprintln!(
"{}:{} DEBUG: {}",
loc.file.name(),
loc.begin.line + 1,
message.inspect(debug_rule.span)?
);
self.options.logger.debug(loc, message.as_str());

Ok(None)
}
Expand Down Expand Up @@ -1588,13 +1584,7 @@ impl<'a> Visitor<'a> {
return;
}
let loc = self.map.look_up_span(span);
eprintln!(
"Warning: {}\n ./{}:{}:{}",
message,
loc.file.name(),
loc.begin.line + 1,
loc.begin.column + 1
);
self.options.logger.warning(loc, message);
}

fn visit_warn_rule(&mut self, warn_rule: AstWarn) -> SassResult<()> {
Expand Down
2 changes: 2 additions & 0 deletions crates/compiler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ pub use crate::error::{
PublicSassErrorKind as ErrorKind, SassError as Error, SassResult as Result,
};
pub use crate::fs::{Fs, NullFs, StdFs};
pub use crate::logger::{Logger, NullLogger, StdLogger};
pub use crate::options::{InputSyntax, Options, OutputStyle};
pub use crate::{builtin::Builtin, evaluate::Visitor};
pub(crate) use crate::{context_flags::ContextFlags, lexer::Token};
Expand Down Expand Up @@ -114,6 +115,7 @@ mod evaluate;
mod fs;
mod interner;
mod lexer;
mod logger;
mod options;
mod parse;
mod selector;
Expand Down
50 changes: 50 additions & 0 deletions crates/compiler/src/logger.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use codemap::SpanLoc;
use std::fmt::Debug;

/// Sink for log messages
pub trait Logger: Debug {
/// Logs message from a `@debug` statement
fn debug(&self, location: SpanLoc, message: &str);

/// Logs message from a `@warn` statement
fn warning(&self, location: SpanLoc, message: &str);
}

/// Logs events to standard error
#[derive(Debug)]
pub struct StdLogger;

impl Logger for StdLogger {
#[inline]
fn debug(&self, location: SpanLoc, message: &str) {
eprintln!(
"{}:{} DEBUG: {}",
location.file.name(),
location.begin.line + 1,
message
);
}

#[inline]
fn warning(&self, location: SpanLoc, message: &str) {
eprintln!(
"Warning: {}\n ./{}:{}:{}",
message,
location.file.name(),
location.begin.line + 1,
location.begin.column + 1
);
}
}

/// Discards all log events
#[derive(Debug)]
pub struct NullLogger;

impl Logger for NullLogger {
#[inline]
fn debug(&self, _location: SpanLoc, _message: &str) {}

#[inline]
fn warning(&self, _location: SpanLoc, _message: &str) {}
}
19 changes: 17 additions & 2 deletions crates/compiler/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{
path::{Path, PathBuf},
};

use crate::{builtin::Builtin, Fs, StdFs};
use crate::{builtin::Builtin, Fs, Logger, StdFs, StdLogger};

/// Configuration for Sass compilation
///
Expand All @@ -12,10 +12,12 @@ use crate::{builtin::Builtin, Fs, StdFs};
#[derive(Debug)]
pub struct Options<'a> {
pub(crate) fs: &'a dyn Fs,
pub(crate) logger: &'a dyn Logger,
pub(crate) style: OutputStyle,
pub(crate) load_paths: Vec<PathBuf>,
pub(crate) allows_charset: bool,
pub(crate) unicode_error_messages: bool,
// TODO: remove in favor of NullLogger
pub(crate) quiet: bool,
pub(crate) input_syntax: Option<InputSyntax>,
pub(crate) custom_fns: HashMap<String, Builtin>,
Expand All @@ -26,6 +28,7 @@ impl Default for Options<'_> {
fn default() -> Self {
Self {
fs: &StdFs,
logger: &StdLogger,
style: OutputStyle::Expanded,
load_paths: Vec::new(),
allows_charset: true,
Expand All @@ -49,6 +52,16 @@ impl<'a> Options<'a> {
self
}

/// This option allows you to define how log events should be handled
///
/// Be default, [`StdLogger`] is used, which writes all events to standard output.
#[must_use]
#[inline]
pub fn logger(mut self, logger: &'a dyn Logger) -> Self {
self.logger = logger;
self
}

/// `grass` currently offers 2 different output styles
///
/// - [`OutputStyle::Expanded`] writes each selector and declaration on its own line.
Expand All @@ -67,10 +80,12 @@ impl<'a> Options<'a> {
/// when compiling. By default, Sass emits warnings
/// when deprecated features are used or when the
/// `@warn` rule is encountered. It also silences the
/// `@debug` rule.
/// `@debug` rule. Setting this option to `true` will
/// stop all events from reaching the assigned [`logger`].
///
/// By default, this value is `false` and warnings are emitted.
#[must_use]
#[deprecated = "use `logger(&NullLogger)` instead"]
#[inline]
pub const fn quiet(mut self, quiet: bool) -> Self {
self.quiet = quiet;
Expand Down
4 changes: 2 additions & 2 deletions crates/lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ grass input.scss
)]

pub use grass_compiler::{
from_path, from_string, Error, ErrorKind, Fs, InputSyntax, NullFs, Options, OutputStyle,
Result, StdFs,
from_path, from_string, Error, ErrorKind, Fs, InputSyntax, Logger, NullFs, NullLogger, Options,
OutputStyle, Result, StdFs, StdLogger,
};

/// Include CSS in your binary at compile time from a Sass source file
Expand Down
43 changes: 34 additions & 9 deletions crates/lib/tests/debug.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,37 @@
use macros::TestLogger;

#[macro_use]
mod macros;

test!(simple_debug, "@debug 2", "");
test!(simple_debug_with_semicolon, "@debug 2;", "");
test!(
// todo: test stdout
debug_while_quiet,
"@debug 2;",
"",
grass::Options::default().quiet(true)
);
#[test]
fn simple_debug() {
let input = "@debug 2";
let logger = TestLogger::default();
let options = grass::Options::default().logger(&logger);
let output = grass::from_string(input.to_string(), &options).expect(input);
assert_eq!(&output, "");
assert_eq!(&[String::from("2")], logger.debug_messages().as_slice());
assert_eq!(&[] as &[String], logger.warning_messages().as_slice());
}

#[test]
fn simple_debug_with_semicolon() {
let input = "@debug 2;";
let logger = TestLogger::default();
let options = grass::Options::default().logger(&logger);
let output = grass::from_string(input.to_string(), &options).expect(input);
assert_eq!(&output, "");
assert_eq!(&[String::from("2")], logger.debug_messages().as_slice());
assert_eq!(&[] as &[String], logger.warning_messages().as_slice());
}

#[test]
fn debug_while_quiet() {
let input = "@debug 2;";
let logger = TestLogger::default();
let options = grass::Options::default().logger(&logger).quiet(true);
let output = grass::from_string(input.to_string(), &options).expect(input);
assert_eq!(&output, "");
assert_eq!(&[] as &[String], logger.debug_messages().as_slice());
assert_eq!(&[] as &[String], logger.warning_messages().as_slice());
}
33 changes: 32 additions & 1 deletion crates/lib/tests/macros.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use std::{
borrow::Cow,
cell::RefCell,
collections::BTreeMap,
path::{Path, PathBuf},
};

use grass::Fs;
use grass::{Fs, Logger};
use grass_compiler::codemap::SpanLoc;

#[macro_export]
macro_rules! test {
Expand Down Expand Up @@ -162,3 +164,32 @@ impl Fs for TestFs {
Ok(self.files.get(path).unwrap().as_bytes().to_vec())
}
}

#[derive(Debug, Default)]
struct TestLoggerState {
debug_messages: Vec<String>,
warning_messages: Vec<String>,
}

#[derive(Debug, Default)]
pub struct TestLogger(RefCell<TestLoggerState>);

impl TestLogger {
pub fn debug_messages(&self) -> Vec<String> {
self.0.borrow().debug_messages.clone()
}

pub fn warning_messages(&self) -> Vec<String> {
self.0.borrow().warning_messages.clone()
}
}

impl Logger for TestLogger {
fn debug(&self, _location: SpanLoc, message: &str) {
self.0.borrow_mut().debug_messages.push(message.into());
}

fn warning(&self, _location: SpanLoc, message: &str) {
self.0.borrow_mut().warning_messages.push(message.into());
}
}
42 changes: 34 additions & 8 deletions crates/lib/tests/warn.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,37 @@
use macros::TestLogger;

#[macro_use]
mod macros;

test!(simple_warn, "@warn 2", "");
test!(
// todo: test stdout
warn_while_quiet,
"@warn 2;",
"",
grass::Options::default().quiet(true)
);
#[test]
fn warn_debug() {
let input = "@warn 2";
let logger = TestLogger::default();
let options = grass::Options::default().logger(&logger);
let output = grass::from_string(input.to_string(), &options).expect(input);
assert_eq!(&output, "");
assert_eq!(&[] as &[String], logger.debug_messages().as_slice());
assert_eq!(&[String::from("2")], logger.warning_messages().as_slice());
}

#[test]
fn simple_warn_with_semicolon() {
let input = "@warn 2;";
let logger = TestLogger::default();
let options = grass::Options::default().logger(&logger);
let output = grass::from_string(input.to_string(), &options).expect(input);
assert_eq!(&output, "");
assert_eq!(&[] as &[String], logger.debug_messages().as_slice());
assert_eq!(&[String::from("2")], logger.warning_messages().as_slice());
}

#[test]
fn warn_while_quiet() {
let input = "@warn 2;";
let logger = TestLogger::default();
let options = grass::Options::default().logger(&logger).quiet(true);
let output = grass::from_string(input.to_string(), &options).expect(input);
assert_eq!(&output, "");
assert_eq!(&[] as &[String], logger.debug_messages().as_slice());
assert_eq!(&[] as &[String], logger.warning_messages().as_slice());
}

0 comments on commit a1ca700

Please sign in to comment.