Skip to content

Move to a Renderer when for displaying a Snippet #67

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Dec 5, 2023
Prev Previous commit
Next Next commit
chore(renderer): Add doc comments
  • Loading branch information
Muscraft committed Dec 5, 2023
commit b0848f70cdf54b4601bd01d4ae99ce4c63a80be9
75 changes: 75 additions & 0 deletions src/renderer/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,37 @@
//! The renderer for [`Snippet`]s
//!
//! # Example
//! ```
//! use annotate_snippets::renderer::Renderer;
//! use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet};
//! let snippet = Snippet {
//! title: Some(Annotation {
//! label: Some("mismatched types"),
//! id: None,
//! annotation_type: AnnotationType::Error,
//! }),
//! footer: vec![],
//! slices: vec![
//! Slice {
//! source: "Foo",
//! line_start: 51,
//! origin: Some("src/format.rs"),
//! fold: false,
//! annotations: vec![],
//! },
//! Slice {
//! source: "Faa",
//! line_start: 129,
//! origin: Some("src/display.rs"),
//! fold: false,
//! annotations: vec![],
//! },
//! ],
//! };
//!
//! let renderer = Renderer::styled();
//! println!("{}", renderer.render(snippet));

mod margin;
pub(crate) mod stylesheet;

Expand All @@ -8,6 +42,7 @@ pub use margin::Margin;
use std::fmt::Display;
use stylesheet::Stylesheet;

/// A renderer for [`Snippet`]s
#[derive(Clone)]
pub struct Renderer {
anonymized_line_numbers: bool,
Expand Down Expand Up @@ -42,56 +77,96 @@ impl Renderer {
}
}

/// Anonymize line numbers
///
/// This enables (or disables) line number anonymization. When enabled, line numbers are replaced
/// with `LL`.
///
/// # Example
///
/// ```text
/// --> $DIR/whitespace-trimming.rs:4:193
/// |
/// LL | ... let _: () = 42;
/// | ^^ expected (), found integer
/// |
/// ```
pub const fn anonymized_line_numbers(mut self, anonymized_line_numbers: bool) -> Self {
self.anonymized_line_numbers = anonymized_line_numbers;
self
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Why do you need those setters, why not make the properties public?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did it because it is easier (and cleaner) to edit one specific field than making the fields public. The other thing is I believe it makes adding a field not a breaking change, but I do not know for certain.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand how is it cleaner. Comparing:

let mut renderer = Renderer::plain();
renderer.anonymized_line_numbers(true);

vs

let mut renderer = Renderer::plain();
renderer.anonymized_line_numbers = true;

Can you explain further?

The other thing is I believe it makes adding a field not a breaking change, but I do not know for certain.

That can be achieved via https://doc.rust-lang.org/reference/attributes/type_system.html#the-non_exhaustive-attribute

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true I forgot about that. I will switch things over

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was looking at making them public, and I realized what I meant by cleaner.

let renderer = Renderer::plain()
    .anonymized_line_numbers(true)
    .margin(margin);

vs

let mut renderer = Renderer::plain();
renderer.anonymized_line_numbers = true;
renderer.margin = margin;

It makes it so that renderer is not mutable and things can be chained together (omitting renderer each time).

It follows the builder pattern which I am a fan for smaller structs, for larger structs I do think making fields public is a good idea.

If you would still like me to change it to be public fields I can but it will take me a moment to refactor.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes it so that renderer is not mutable and things can be chained together (omitting renderer each time).

That's not correct. The renderer still has to be mutable for the setters to work (you're consuming them btw. mut self).

The builder pattern is useful when your setters actually perform computations, invariant validation etc. In this case, your builder is the struct itself, so exposing it as such allows customers to use any Rust patterns to construct it.

By hiding it you're relying on custom uninspectable API to achieve the same.

It's a glorified version of:

struct Point {
    x: f64,
    y: f64
}

impl Point {
    fn new(x: f64, y: f64) -> Self {
        Self { x, y }    
    }

    fn set_x(&mut self, x: f64) -> &mut Self {
        self.x = x;
        self
    }

    fn set_y(&mut self, y: f64) -> &mut Self {
        self.y = y;
        self
    }
}

vs just:

struct Point {
    pub x: f64,
    pub y: f64,
}

and let customers create it and operate on it any way they want. You can add helper impl methods (like you do with plain), but taking away ability to operate on the fields without any reason seems like a way that limits your downstream consumers from being able to interact with the struct in ways that you don't necessarily have to be able to even predict.

In other words, if at the moment of consumption, you don't care how this struct has been constructred (maybe someone created their own methods to build the Renderer, or merge two), and you don't perform any validation, or variant locking, then just let it be an open struct and let people use all of Rust expressiveness to operate on it.

Saying that - this is my opinion, not a strong requirement - I will not try to block your PR on that, so if my arguments are not convincing to you, continue with this API and I'll r+ it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats within the same visibility. I don't think you can if you don't have visibility.

One benefit I've seen to builder is the ability to evolve an API, particularly for evolving styles. You'll also see builder used in a lot of the ecosystem when no validation is done.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good call. Yeah, ability to use .. would be nice.

I'm generally supportive of builder patterns when there's logic involved. I find it inelegant when we have a plain struct and we populate it with dummy getters/setters.

But as I stated, this is my preference, and I'm not intending to push for it any further. I support the direction the author of the PR decides to take.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see the merits of both and could go either way on this. Even though it is a breaking change, we could always change in the future. I'd say we should go with the builder (for now).


/// Set the margin for the output
///
/// This controls the various margins of the output.
///
/// # Example
///
/// ```text
/// error: expected type, found `22`
/// --> examples/footer.rs:29:25
/// |
/// 26 | ... annotations: vec![SourceAnnotation {
/// | ---------------- info: while parsing this struct
/// ...
/// 29 | ... range: <22, 25>,
/// | ^^
/// |
/// ```
pub const fn margin(mut self, margin: Option<Margin>) -> Self {
self.margin = margin;
self
}

/// Set the output style for `error`
pub const fn error(mut self, style: Style) -> Self {
self.stylesheet.error = style;
self
}

/// Set the output style for `warning`
pub const fn warning(mut self, style: Style) -> Self {
self.stylesheet.warning = style;
self
}

/// Set the output style for `info`
pub const fn info(mut self, style: Style) -> Self {
self.stylesheet.info = style;
self
}

/// Set the output style for `note`
pub const fn note(mut self, style: Style) -> Self {
self.stylesheet.note = style;
self
}

/// Set the output style for `help`
pub const fn help(mut self, style: Style) -> Self {
self.stylesheet.help = style;
self
}

/// Set the output style for line numbers
pub const fn line_no(mut self, style: Style) -> Self {
self.stylesheet.line_no = style;
self
}

/// Set the output style for emphasis
pub const fn emphasis(mut self, style: Style) -> Self {
self.stylesheet.emphasis = style;
self
}

/// Set the output style for none
pub const fn none(mut self, style: Style) -> Self {
self.stylesheet.none = style;
self
}

/// Render a snippet into a `Display`able object
pub fn render<'a>(&'a self, snippet: Snippet<'a>) -> impl Display + 'a {
DisplayList::new(
snippet,
Expand Down