-
Notifications
You must be signed in to change notification settings - Fork 36
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
Move to a Renderer
when for displaying a Snippet
#67
Conversation
Note that the biggest reason to use anstyle is that it is designed primarily to exist in public APIs for interoperability. Use for styling is secondary and there exist adapters to styling APIs |
Note that the biggest reason to use anstyle is that it is designed primarily to exist in public APIs for interoperability. Use for styling is secondary and there exist adapters to styling APIs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm!
stylesheet: StyleSheet, | ||
} | ||
|
||
impl Renderer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
praise
: I like that abstraction. Great addition!
pub const fn anonymized_line_numbers(mut self, anonymized_line_numbers: bool) -> Self { | ||
self.anonymized_line_numbers = anonymized_line_numbers; | ||
self | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm!
impl Margin { | ||
pub fn new( | ||
whitespace_left: usize, | ||
span_left: usize, | ||
span_right: usize, | ||
label_right: usize, | ||
column_width: usize, | ||
max_line_len: usize, | ||
) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the future, we might want to consider if there are improvements to this API as well
BREAKING CHANGE: This removes `opt` from `Snippet`
BREAKING CHANGE: This removes the `formatter` and `stylesheets` modules
BREAKING CHANGE: This removes `color` feature
BREAKING CHANGE: This moves `snippet::*` and `Renderer` to root
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: refactors can't have breaking changes, so I'd change the commit types away from it where user-visible changes are made
This PR moves to using
Renderer
as the external type for handling displaying aSnippet
.This has a few advantages:
Snippet
Snippet
and what is going to be "rendering" itThis PR also:
anstyle
for color and removes thecolor
featureanstyle
is lightweight and writes nothing if the color isStyle::new
anstyle
is whatcargo
uses and it would be good if when we use this crate we only need one color library.Note: this is a step in the direction of the proposals for a more modern API (#12 and #13). I have been taking inspiration from both PRs to get a clear vision of what the future should look like.
To review this PR, look at it commit by commit