Add logger trait (#93)
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.
This commit is contained in:
parent
8d3258dcd4
commit
a1ca700bff
@ -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)
|
||||
}
|
||||
@ -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<()> {
|
||||
|
@ -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};
|
||||
@ -114,6 +115,7 @@ mod evaluate;
|
||||
mod fs;
|
||||
mod interner;
|
||||
mod lexer;
|
||||
mod logger;
|
||||
mod options;
|
||||
mod parse;
|
||||
mod selector;
|
||||
|
50
crates/compiler/src/logger.rs
Normal file
50
crates/compiler/src/logger.rs
Normal file
@ -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) {}
|
||||
}
|
@ -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
|
||||
///
|
||||
@ -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>,
|
||||
@ -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,
|
||||
@ -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.
|
||||
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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 {
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user