Skip to content

Add filter |default #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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

Conversation

Kijewski
Copy link
Member

@Kijewski Kijewski commented May 1, 2025

Also, enum Pluralize<S, P> is renamed into enum Either<L, R> and exported.

Cc @joshka.

Resolves #424.

@Kijewski
Copy link
Member Author

Kijewski commented May 1, 2025

I am not sure if DefaultUnwrappable is a good name.

Also, it would be possible to implement the trait for integers (testing if != 0), and Strings (testing if != "").

@Kijewski Kijewski force-pushed the pr-default branch 2 times, most recently from 4af0d79 to 90c531c Compare May 1, 2025 11:54
GuillaumeGomez
GuillaumeGomez previously approved these changes May 1, 2025
@GuillaumeGomez
Copy link
Collaborator

Btw, no clue about a better for DefaultUnwrappable so I'm fine with it.

</li><li>

```jinja
{{ variable_or_expression | default(default_value, true) }}
Copy link

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:

  1. Replace a naked bool parameter with an enum that self documents (that may look verbose here), or
  2. 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

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 added a line that {{ variable_or_expression | default(default_value, boolean = true) }} works, too.

Copy link

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.

@joshka
Copy link

joshka commented May 1, 2025

Also, it would be possible to implement the trait for integers (testing if != 0), and Strings (testing if != "").

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.

@Kijewski
Copy link
Member Author

Kijewski commented May 1, 2025

  • Renamed trait to DefaultFilterable (I think that's better)
  • Implemented for Cow<'_, T> (I forgot about taat, good call!)
  • Implemented for Wrapping<T> & Saturating<T>
  • Implemented for NonZero* (infallably Ok(Some())) [just so you don't have to update the code if you use NonZeroU32 and u32]

I think implementing the type for integers is fine and useful. In many real-world cases you will find 0 being used as a poor-man's NULL. And unless boolean = true, it does not matter either way.

DefaultFilterable is not recursive: Some(0) returns 0; only the Option is evaluated, not the contained value.

Comment on lines -333 to +334
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>
Copy link

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?

Copy link
Collaborator

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.

Copy link
Member Author

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 _: () = {
Copy link

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?

Copy link
Collaborator

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.

Copy link
Member Author

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.

</li><li>

```jinja
{{ variable_or_expression | default(default_value, true) }}
Copy link

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",
Copy link
Collaborator

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?

@Kijewski
Copy link
Member Author

Kijewski commented May 2, 2025

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:

  1. I would then call the boolean = false option {{ var | defined_or(default_value) }}.
  2. And the boolean = true option {{ expr | assigned_or(default_value) }}.

Fun fact minijinja does not implement |default either.

Also, `enum Pluralize<S, P>` is renamed into `enum Either<L, R>` and
exported.
@Kijewski
Copy link
Member Author

Kijewski commented May 2, 2025

Or, what about both options?

  • {{ var | default(default_value, [false]) }}{{ var | defined_or(default_value) }}
  • {{ expr | default(default_value, [boolean =] true) }}{{ expr | assigned_or(default_value) }}

The filter |default will work as in jinja, same semantics, name argument naming; but you could also use filters with a (hopefully) better naming scheme.

@GuillaumeGomez
Copy link
Collaborator

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, askama is using X name instead for this named argument.

What do you think @joshka?

@joshka
Copy link

joshka commented May 3, 2025

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add filter to simplify handling of Option fields
3 participants