templater: add Template::has_content() to filter out empty expressions

This allows us to insert a separator between non-empty template fragments.
Since FormattablePropertyTemplate::has_content() needs to extract
a TemplateProperty, using this function means the property function will
be evaluated twice, one by .has_content() and later by .format(). The cost
is basically the same as 'if(prop, " " prop)'.
This commit is contained in:
Yuya Nishihara 2023-02-03 13:56:56 +09:00
parent 5850575d53
commit 84ee0edc51
2 changed files with 61 additions and 0 deletions

View file

@ -103,6 +103,10 @@ fn cmd_op_log(
}
Ok(())
}
fn has_content(&self, _: &Operation) -> bool {
true
}
}
let template = OpTemplate {
relative_timestamps: command.settings().relative_timestamps(),

View file

@ -25,12 +25,18 @@ use crate::time_util;
pub trait Template<C> {
fn format(&self, context: &C, formatter: &mut dyn Formatter) -> io::Result<()>;
/// Returns true if `format()` will generate output other than labels.
fn has_content(&self, context: &C) -> bool;
}
impl<C, T: Template<C> + ?Sized> Template<C> for Box<T> {
fn format(&self, context: &C, formatter: &mut dyn Formatter) -> io::Result<()> {
<T as Template<C>>::format(self, context, formatter)
}
fn has_content(&self, context: &C) -> bool {
<T as Template<C>>::has_content(self, context)
}
}
impl Template<()> for Signature {
@ -41,24 +47,40 @@ impl Template<()> for Signature {
write!(formatter, ">")?;
Ok(())
}
fn has_content(&self, _: &()) -> bool {
true
}
}
impl Template<()> for String {
fn format(&self, _: &(), formatter: &mut dyn Formatter) -> io::Result<()> {
formatter.write_str(self)
}
fn has_content(&self, _: &()) -> bool {
!self.is_empty()
}
}
impl Template<()> for Timestamp {
fn format(&self, _: &(), formatter: &mut dyn Formatter) -> io::Result<()> {
formatter.write_str(&time_util::format_absolute_timestamp(self))
}
fn has_content(&self, _: &()) -> bool {
true
}
}
impl Template<()> for bool {
fn format(&self, _: &(), formatter: &mut dyn Formatter) -> io::Result<()> {
formatter.write_str(if *self { "true" } else { "false" })
}
fn has_content(&self, _: &()) -> bool {
true
}
}
pub struct LabelTemplate<T, L> {
@ -92,6 +114,10 @@ where
}
Ok(())
}
fn has_content(&self, context: &C) -> bool {
self.content.has_content(context)
}
}
pub struct ListTemplate<T>(pub Vec<T>);
@ -103,6 +129,10 @@ impl<C, T: Template<C>> Template<C> for ListTemplate<T> {
}
Ok(())
}
fn has_content(&self, context: &C) -> bool {
self.0.iter().any(|template| template.has_content(context))
}
}
pub trait TemplateProperty<C> {
@ -126,6 +156,10 @@ impl<C, O: Template<()>> Template<C> for Literal<O> {
fn format(&self, _context: &C, formatter: &mut dyn Formatter) -> io::Result<()> {
self.0.format(&(), formatter)
}
fn has_content(&self, _context: &C) -> bool {
self.0.has_content(&())
}
}
impl<C, O: Clone> TemplateProperty<C> for Literal<O> {
@ -171,6 +205,11 @@ where
let template = self.property.extract(context);
template.format(&(), formatter)
}
fn has_content(&self, context: &C) -> bool {
let template = self.property.extract(context);
template.has_content(&())
}
}
/// Adapter to turn template back to string property.
@ -367,6 +406,16 @@ where
}
Ok(())
}
fn has_content(&self, context: &C) -> bool {
if self.condition.extract(context) {
self.true_template.has_content(context)
} else if let Some(false_template) = &self.false_template {
false_template.has_content(context)
} else {
false
}
}
}
// TODO: If needed, add a ContextualTemplateFunction where the function also
@ -459,6 +508,10 @@ impl Template<()> for CommitOrChangeId<'_> {
fn format(&self, _: &(), formatter: &mut dyn Formatter) -> io::Result<()> {
formatter.write_str(&self.hex())
}
fn has_content(&self, _: &()) -> bool {
!self.id_bytes.is_empty()
}
}
/// This function supports short `total_len` by ensuring that the entire
@ -517,4 +570,8 @@ impl Template<()> for IdWithHighlightedPrefix {
formatter.with_label("prefix", |fmt| fmt.write_str(&self.prefix))?;
formatter.with_label("rest", |fmt| fmt.write_str(&self.rest))
}
fn has_content(&self, _: &()) -> bool {
!self.prefix.is_empty() || !self.rest.is_empty()
}
}