Skip to content
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

Support Formatter::alternate (the # in format string) for another display variant #41

Open
nyurik opened this issue Feb 11, 2024 · 3 comments

Comments

@nyurik
Copy link
Contributor

nyurik commented Feb 11, 2024

Oxigraph (SPAQRL RDF engine) has two representations of every enum value, e.g. Function::Replace has to be formatted as either replace or REPLACE depending on the usecase. Currently, Oxigraph has a regular fmt::Display implementation with a giant match statement to format the upper case variant, and another function fn fmt_sse(&self, f: &mut impl fmt::Write) -- that looks identical to the Display, but is used by another code path.

I would like to propose parse-display to add support for multiple display variants. When present, parse-display can generate this type of code, which will be used by format!("{func}") vs format!("{func:#}")

// My code
#[derive(parse_display::Display)]
#[display(style = "UPPERCASE", alternate_style = "lowercase")]
enum Function {
  Replace,
  // ...
}

// Generated code
impl fmt::Display for Function {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    if f.alternate() {
      match self {
          Self::Replace => f.write_str("replace"),
          // ...
      }
    } else {
      match self {
          Self::Replace => f.write_str("REPLACE"),
          // ...
      }
    }
  }
}

CC: @Tpt

@frozenlib
Copy link
Owner

Thanks. I think it is a good idea.

I had not considered the "alternate" form at all until now.

I gave it some thought and thought the following points also need to be considered

  • Allowing additional format string for "alternate" form
  • Propagating # flag when use the field's format trait
  • Generate code for FromStr that can also parse "alternate" form

@nyurik
Copy link
Contributor Author

nyurik commented Feb 11, 2024

I guess alt is a better word - shorter, and well established in HTML. Can even support multiple variants:

// all on one line
#[display("value {} = {0}", alt = "Value {} is {0}", style = "UPPERCASE", alt_style = "TitleCase")]

// dedicated #[alt_display(...)] attribute
#[display("value {} = {0}", style = "UPPERCASE")]
#[alt_display("value {} = {0}", style = "TitleCase")]

// Re-use the same attribute multiple times, allowing each to append values (might want to detect dups?)
#[display("value {} = {0}", style = "UPPERCASE")]
#[display(alt = "value {} = {0}", alt_style = "TitleCase")]

I was also thinking of the best code to generate. At the fn root, it could be one of these (assuming the enum has just one variant:

// Two match statements
if f.alternate() {
  match self {
    Self::Replace => f.write_str("replace"),
  }
} else {
  match self {
    Self::Replace => f.write_str("REPLACE"),
  }
}

// --------------------------------------------------------------------------
// One match, each variant has its own    if f.alternate() {...} else {...}
match self {
  Self::Replace => if f.alternate() {
    f.write_str("replace")
  } else {
    f.write_str("REPLACE")
  }
}

Some possible optimizations (not sure if worth it, or if compiler will automatically do them):

  • with one match approach, if both primary and alt displays are identical, remove the if statement
  • if all variants use constant strings, place f.write_str just once at the top of the fmt function, and use match statement inside of it - e.g. f.write_str( match self { Self::Replace => "replace", ... } ) (all the if alt tests will go inside the match statement as well)

@frozenlib
Copy link
Owner

As another option, I considered specifying multiple settings in a tuple, as shown below.

#[display(style = ("UPPERCASE", "TitleCase"))]

It is concise, but difficult to use with arguments that can specify expressions, and the lack of keywords such as alt makes it hard to imagine what it means.
So it may be better to use alt.

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

No branches or pull requests

2 participants