-
Notifications
You must be signed in to change notification settings - Fork 27
Add filters |default
, |assigned_or
and |defined_or
#425
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
base: master
Are you sure you want to change the base?
Conversation
I am not sure if Also, it would be possible to implement the trait for integers (testing if != 0), and Strings (testing if != ""). |
4af0d79
to
90c531c
Compare
Btw, no clue about a better for |
book/src/filters.md
Outdated
</li><li> | ||
|
||
```jinja | ||
{{ variable_or_expression | default(default_value, true) }} |
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.
This sort of parameter without a name makes it difficult to understand a template worth reading the template docs. I'd been inclined to either:
- Replace a naked bool parameter with an enum that self documents (that may look verbose here), or
- Replace a bool with a second named method. E.g. default_if_undefined might be a good name for the false branch. Alternatively default_if_none / err
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 added a line that {{ variable_or_expression | default(default_value, boolean = true) }}
works, too.
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'd still prefer explicit over implicit a bit. This is based on an assumption that the direction of askama is a jinja-like template library with natural rust idioms rather than a jinja compatible template library that adds rust conveniences. I'm not sure whether that assumption is correct however (intuitively I'd lean towards the former as being more useful)
As a single data point. Having not seen the jinja syntax for the default
filter prior, even with the parameter named boolean
in the CoPilot suggestion rather than the bare true
, I still had no idea from reading it what that parameter meant or controlled seeing it for the first time. This suggests to me that it is poorly named in jinja, and something that we could make better pretty easily with good naming here.
Empty string seems reasonable as that's rationally returned when a string is not entered somewhere, but most of the time a zero value seems like it would be only 0 if you're using that as a flag value, which is not very rusty, so I'd perhaps implement default_if_empty for strings (and str, cow perhaps), but not default_if_zero unless there's an explicit obvious use case where it makes sense. |
I think implementing the type for integers is fine and useful. In many real-world cases you will find
|
pub fn pluralize<C, S, P>(count: C, singular: S, plural: P) -> Result<Pluralize<S, P>, C::Error> | ||
pub fn pluralize<C, S, P>(count: C, singular: S, plural: P) -> Result<Either<S, P>, C::Error> |
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.
Does this break semver (in a way that matters) for any consumers?
If so, would be worth keeping as Pluralize rather than version bumping the lib?
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.
Well, version bumping will be needed since we plan to extend {% call %}
blocks. So it's fine.
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.
The type was unreachable before, so its name did not matter.
fn as_filtered(&self) -> Result<Option<Self::Filtered<'_>>, Self::Error>; | ||
} | ||
|
||
const _: () = { |
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.
This isn't a syntax I've seen before, what's the rationale behind 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.
To prevent impacting the upper scope.
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 here I mostly use it like a #region
. Every DefaultFilterable
is in this scope and one click hides everything.
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.
Got it. I know that often there's a tendency for larger module sizes in some Rust projects. This seems to be a symptom of that. I tend to address the same core problem (strongly related code that isn't in the outer scope) by using smaller narrower module (files) and liberal re-exports instead. This keeps the unit tests fairly close to the code it's testing, makes it easy to navigate and find code, etc. Obv. this is subjective, but maybe something to think about in future.
So a soft suggestion to extract this to filters/default.rs or something.
book/src/filters.md
Outdated
</li><li> | ||
|
||
```jinja | ||
{{ variable_or_expression | default(default_value, true) }} |
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'd still prefer explicit over implicit a bit. This is based on an assumption that the direction of askama is a jinja-like template library with natural rust idioms rather than a jinja compatible template library that adds rust conveniences. I'm not sure whether that assumption is correct however (intuitively I'd lean towards the former as being more useful)
As a single data point. Having not seen the jinja syntax for the default
filter prior, even with the parameter named boolean
in the CoPilot suggestion rather than the bare true
, I still had no idea from reading it what that parameter meant or controlled seeing it for the first time. This suggests to me that it is poorly named in jinja, and something that we could make better pretty easily with good naming here.
default_value: None, | ||
}, | ||
&FilterArgument { | ||
name: "boolean", |
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.
Why not naming it "do_unwrap" or something along the line to better match what it's doing instead?
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 still open. :)
So, what do we want to do with the argument "boolean"? It's the name and semantics Jinja uses. The question is: How compatible do we want to be? If we don't care to be 100% compatible (and we don't), then yes, two filters would be the better option:
Fun fact minijinja does not implement |
Or, what about both options?
The filter |
Having two filters for the same thing sounds good to me. 👍 Just please improve the argument name. We can add an error for this specific case to tell that for better naming, What do you think @joshka? |
Supporting jinja syntax probably makes it easier to bring templates from elsewhere (and allows LLMs to fall into the pit of success by suggesting code which works even if it's sub optimal). So while I dislike the syntax, if there's a better syntax available like suggested above, then I've no complaints about also providing compatible syntax like this. I'd even go so far as to suggest that the parameter name is fine in that case (alternatively, if there's some future ability to alias it to give it an additional more descriptive name then that would work too) |
|default
|default
, |assigned_or
and |defined_or
eef03df
to
826194d
Compare
I added I don't think that it would be a good idea to implement
|
The other names are good. I like the idea of making it possible to do something useful if someone tries jinja syntax. That makes a lot of sense as it makes it easy to fall into a pit of success. But I also like the idea of making this a compile error, but wonder if it might be possible to cause a deprecation warning to be emitted for |
I could not think of a use case. In python, the Right now, a proc-macro can only emit fatal errors, no warnings. There are open RFCs to add #[deprecated(note = "use `|defined_or` or `|assigned_or` instead of `|default`")]
fn dont_use_default() {} Most warnings that come from proc-macros get suppressed, though. I would need to test if deprecation message are one of them. |
Also, `enum Pluralize<S, P>` is renamed into `enum Either<L, R>` and exported.
We can't assign a span to the warning (→ #420). Without the span, such a warning is kinda useless IMHO. I think the current implementation is fine as it is. :) We can always add a warning in a subsequent PR. @GuillaumeGomez, @joshka, what do you think? Good enough to be merged? |
Output looks ok to me. |
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.
Some extra rview notes having gone through this in a bit more detail.
fn as_filtered(&self) -> Result<Option<Self::Filtered<'_>>, Self::Error>; | ||
} | ||
|
||
const _: () = { |
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.
Got it. I know that often there's a tendency for larger module sizes in some Rust projects. This seems to be a symptom of that. I tend to address the same core problem (strongly related code that isn't in the outer scope) by using smaller narrower module (files) and liberal re-exports instead. This keeps the unit tests fairly close to the code it's testing, makes it easy to navigate and find code, etc. Obv. this is subjective, but maybe something to think about in future.
So a soft suggestion to extract this to filters/default.rs or something.
label = "`{Self}` is not `|assigned_or` filterable", | ||
message = "`{Self}` is not `|assigned_or` filterable" | ||
)] | ||
pub trait DefaultFilterable { |
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 could be worth adding a unit tests for the various implementations of this that captures each implementation. E.g.:
assert_eq!(0_u8.as_filtered(), Ok(None));
assert_eq!(1_u8.as_filtered(), Ok(Some(1));
|
||
```jinja | ||
{% let greeting = Some("Hello") %} | ||
{{ greeting.as_ref() | default("Hi", true) }} |
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.
should be
{{ greeting.as_ref() | default("Hi", true) }} | |
{{ greeting.as_ref() | assigned_or("Hi") }} |
{{ variable_or_expression | assigned_or(fallback) }} | ||
{{ variable_or_expression | assigned_or(fallback) }} |
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.
duplicate line - needs to be fixed or state what's going on here.
This filter works like the Jinja filter of the same name. | ||
If the second argument is not an boolean `true`, then the filter behaves like [`|defined_or`][#defined_or]. | ||
If it is supplied and `true`, then the filter behaves like [`|assigned_or`][#assigned_or]. |
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.
This should maybe describe how this works without having to link to the jinja / the other filters.
If it is supplied and `true`, then the filter behaves like [`|assigned_or`][#assigned_or]. | ||
|
||
**This filter exists for compatibility with Jinja. | ||
You should use [`|defined_or`][#defined_or] and [`|assigned_or`][#assigned_or] directly instead.** |
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.
You should use [`|defined_or`][#defined_or] and [`|assigned_or`][#assigned_or] directly instead.** | |
Askama provides [`|defined_or`][#defined_or] and [`|assigned_or`][#assigned_or] which both better express the intention and should generally usually be used instead of this filter.** |
"var | default }}" | ||
--> tests/ui/default.rs:10:35 | ||
| | ||
10 | #[template(ext = "html", source = "{{ var | default }}")] |
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.
This actually suggests a bit of a jinja / rust impedance mismatch here. In Rust, we have a few different operations like map_or_default
and unwrap_or_default
. Intuitively, var | default
when the type of var implements the Default
trait seems like it would be obvious that it should return default. I know that a user could call .unwrap_or_default()
or whatever there, but I'm curious whether it might be worth extending this to allow the obvious intuitive thing there. (I'm not fully certain of this, either way, WDYT?)
@joshka, thank you for your suggestions and proofreading! I will have a look tomorrow. |
Also,
enum Pluralize<S, P>
is renamed intoenum Either<L, R>
and exported.Cc @joshka.
Resolves #424.