1170 lines · 33.4 KB · Rust
RawDownload
// Copyright 2020 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. //! Tools for lazily evaluating templates that produce text in a fallible//! manner. use std::cell::RefCell;use std::collections::HashMap;use std::error;use std::fmt;use std::io;use std::io::Write;use std::iter;use std::ops::Range;use std::path::PathBuf;use std::rc::Rc; use bstr::BStr;use bstr::BString;use jj_lib::backend::Signature;use jj_lib::backend::Timestamp;use jj_lib::config::ConfigValue;use jj_lib::file_util;use jj_lib::op_store::TimestampRange; use crate::formatter::FormatRecorder;use crate::formatter::Formatter;use crate::formatter::FormatterExt as _;use crate::formatter::LabeledScope;use crate::formatter::PlainTextFormatter;use crate::text_util;use crate::time_util; /// Represents a printable type or a compiled template containing a placeholder/// value.////// This is analogous to [`std::fmt::Display`], but with customized error/// handling.pub trait Template {    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()>;} impl<T: Template + ?Sized> Template for &T {    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        <T as Template>::format(self, formatter)    }} impl<T: Template + ?Sized> Template for Box<T> {    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        <T as Template>::format(self, formatter)    }} // All optional printable types should be printable, and it's unlikely to// implement different formatting per type.impl<T: Template> Template for Option<T> {    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        self.as_ref().map_or(Ok(()), |t| t.format(formatter))    }} impl Template for BString {    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        formatter.as_mut().write_all(self)    }} impl Template for &BStr {    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        formatter.as_mut().write_all(self)    }} impl Template for PathBuf {    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        // Render native path bytes directly. Serialization still follows        // PathBuf's serde behavior, which can reject non-UTF-8 paths.        let bytes = file_util::path_to_bytes(self).map_err(io::Error::other)?;        formatter.as_mut().write_all(bytes)    }} impl Template for ConfigValue {    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        write!(formatter, "{self}")    }} impl Template for Signature {    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        write!(formatter.labeled("name"), "{}", self.name)?;        if !self.name.is_empty() && !self.email.is_empty() {            write!(formatter, " ")?;        }        if !self.email.is_empty() {            write!(formatter, "<")?;            let email = Email(self.email.clone());            email.format(formatter)?;            write!(formatter, ">")?;        }        Ok(())    }} #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]#[serde(transparent)]pub struct Email(pub String); impl Template for Email {    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        let (local, domain) = text_util::split_email(&self.0);        write!(formatter.labeled("local"), "{local}")?;        if let Some(domain) = domain {            write!(formatter, "@")?;            write!(formatter.labeled("domain"), "{domain}")?;        }        Ok(())    }} // In template language, an integer value is represented as i64. However, we use// usize here because it's more convenient to guarantee that the lower value is// bounded to 0.pub type SizeHint = (usize, Option<usize>); /// Captures from a regex match, accessible by index or name.#[derive(Clone, Debug)]pub struct RegexCaptures {    /// String that matches were found in.    haystack: Vec<u8>,    /// List of byte ranges in `haystack` for capture groups by index (with 0    /// being the full match).    capture_ranges: Vec<Range<usize>>,    /// Mapping from capture group names to their index.    names: HashMap<String, usize>,} impl RegexCaptures {    pub fn new(        haystack: Vec<u8>,        capture_ranges: Vec<Range<usize>>,        names: HashMap<String, usize>,    ) -> Self {        Self {            haystack,            capture_ranges,            names,        }    }     #[expect(clippy::len_without_is_empty)]    pub fn len(&self) -> usize {        self.capture_ranges.len()    }     pub fn get(&self, index: usize) -> Option<BString> {        self.capture_ranges            .get(index)            .map(|range| self.haystack[range.start..range.end].into())    }     pub fn name(&self, name: &str) -> Option<BString> {        self.names.get(name).and_then(|&i| self.get(i))    }} impl Template for String {    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        write!(formatter, "{self}")    }} impl Template for &str {    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        write!(formatter, "{self}")    }} impl Template for Timestamp {    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        match time_util::format_absolute_timestamp(self) {            Ok(formatted) => write!(formatter, "{formatted}"),            Err(err) => formatter.handle_error(err.into()),        }    }} impl Template for TimestampRange {    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        self.start.format(formatter)?;        write!(formatter, " - ")?;        self.end.format(formatter)?;        Ok(())    }} impl Template for Vec<BString> {    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        format_joined(formatter, self, " ")    }} impl Template for Vec<String> {    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        format_joined(formatter, self, " ")    }} impl Template for bool {    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        let repr = if *self { "true" } else { "false" };        write!(formatter, "{repr}")    }} impl Template for i64 {    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        write!(formatter, "{self}")    }} pub struct LabelTemplate<T, L> {    content: T,    labels: L,} impl<T, L> LabelTemplate<T, L> {    pub fn new(content: T, labels: L) -> Self    where        T: Template,        L: TemplateProperty<Output = Vec<String>>,    {        Self { content, labels }    }} impl<T, L> Template for LabelTemplate<T, L>where    T: Template,    L: TemplateProperty<Output = Vec<String>>,{    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        match self.labels.extract() {            Ok(labels) => format_labeled(formatter, &self.content, &labels),            Err(err) => formatter.handle_error(err),        }    }} pub struct RawEscapeSequenceTemplate<T>(pub T); impl<T: Template> Template for RawEscapeSequenceTemplate<T> {    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        let rewrap = formatter.rewrap_fn();        let mut raw_formatter = PlainTextFormatter::new(formatter.raw()?);        self.0.format(&mut rewrap(&mut raw_formatter))    }} /// Renders a hyperlink using OSC 8 escape sequences when supported, or a/// fallback otherwise.pub struct HyperlinkTemplate<U, T, F> {    url: U,    text: T,    fallback: Option<F>,} impl<U, T, F> HyperlinkTemplate<U, T, F> {    pub fn new(url: U, text: T, fallback: Option<F>) -> Self {        Self {            url,            text,            fallback,        }    }} impl<U, T, F> Template for HyperlinkTemplate<U, T, F>where    U: TemplateProperty<Output = String>,    T: Template,    F: Template,{    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        // Extract URL string        let url_str = match self.url.extract() {            Ok(url) => url,            Err(err) => return formatter.handle_error(err),        };         if !formatter.maybe_color() {            if let Some(fallback) = &self.fallback {                return fallback.format(formatter);            }            return self.text.format(formatter);        }         // Write OSC 8 hyperlink via raw()        write!(formatter.raw()?, "\x1b]8;;{url_str}\x1b\\")?;        self.text.format(formatter)?;        write!(formatter.raw()?, "\x1b]8;;\x1b\\")    }} /// Renders contents in order, and returns the first non-empty output.pub struct CoalesceTemplate<T>(pub Vec<T>); impl<T: Template> Template for CoalesceTemplate<T> {    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        let Some((last, contents)) = self.0.split_last() else {            return Ok(());        };        let record_non_empty = record_non_empty_fn(formatter);        if let Some(recorder) = contents.iter().find_map(record_non_empty) {            recorder?.replay(formatter.as_mut())        } else {            last.format(formatter) // no need to capture the last content        }    }} pub struct ConcatTemplate<T>(pub Vec<T>); impl<T: Template> Template for ConcatTemplate<T> {    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        for template in &self.0 {            template.format(formatter)?;        }        Ok(())    }} /// Renders the content to buffer, and transforms it without losing labels.pub struct ReformatTemplate<T, F> {    content: T,    reformat: F,} impl<T, F> ReformatTemplate<T, F> {    pub fn new(content: T, reformat: F) -> Self    where        T: Template,        F: Fn(&mut TemplateFormatter, &FormatRecorder) -> io::Result<()>,    {        Self { content, reformat }    }} impl<T, F> Template for ReformatTemplate<T, F>where    T: Template,    F: Fn(&mut TemplateFormatter, &FormatRecorder) -> io::Result<()>,{    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        let rewrap = formatter.rewrap_fn();        let mut recorder = FormatRecorder::new(formatter.maybe_color());        self.content.format(&mut rewrap(&mut recorder))?;        (self.reformat)(formatter, &recorder)    }} /// Like `ConcatTemplate`, but inserts a separator between contents.pub struct JoinTemplate<S, T> {    separator: S,    contents: Vec<T>,} impl<S, T> JoinTemplate<S, T> {    pub fn new(separator: S, contents: Vec<T>) -> Self    where        S: Template,        T: Template,    {        Self {            separator,            contents,        }    }} impl<S, T> Template for JoinTemplate<S, T>where    S: Template,    T: Template,{    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        format_joined(formatter, &self.contents, &self.separator)    }} /// Like `JoinTemplate`, but ignores empty contents.pub struct SeparateTemplate<S, T> {    separator: S,    contents: Vec<T>,} impl<S, T> SeparateTemplate<S, T> {    pub fn new(separator: S, contents: Vec<T>) -> Self    where        S: Template,        T: Template,    {        Self {            separator,            contents,        }    }} impl<S, T> Template for SeparateTemplate<S, T>where    S: Template,    T: Template,{    fn format(&self, formatter: &mut TemplateFormatter) -> io::Result<()> {        let record_non_empty = record_non_empty_fn(formatter);        let content_recorders = self.contents.iter().filter_map(record_non_empty);        format_joined_with(            formatter,            content_recorders,            &self.separator,            |formatter, recorder| recorder?.replay(formatter.as_mut()),        )    }} /// Wrapper around an error occurred during template evaluation.#[derive(Debug)]pub struct TemplatePropertyError(pub Box<dyn error::Error + Send + Sync>); // Implements conversion from any error type to support `expr?` in function// binding. This type doesn't implement `std::error::Error` instead.// <https://github.com/dtolnay/anyhow/issues/25#issuecomment-544140480>impl<E> From<E> for TemplatePropertyErrorwhere    E: error::Error + Send + Sync + 'static,{    fn from(err: E) -> Self {        Self(err.into())    }} /// Lazily evaluated value which can fail to evaluate.pub trait TemplateProperty {    type Output;     fn extract(&self) -> Result<Self::Output, TemplatePropertyError>;} impl<P: TemplateProperty + ?Sized> TemplateProperty for Box<P> {    type Output = <P as TemplateProperty>::Output;     fn extract(&self) -> Result<Self::Output, TemplatePropertyError> {        <P as TemplateProperty>::extract(self)    }} impl<P: TemplateProperty> TemplateProperty for Option<P> {    type Output = Option<P::Output>;     fn extract(&self) -> Result<Self::Output, TemplatePropertyError> {        self.as_ref().map(|property| property.extract()).transpose()    }} // Implement TemplateProperty for tuplesmacro_rules! tuple_impls {    ($( ( $($n:tt $T:ident),+ ) )+) => {        $(            impl<$($T: TemplateProperty,)+> TemplateProperty for ($($T,)+) {                type Output = ($($T::Output,)+);                 fn extract(&self) -> Result<Self::Output, TemplatePropertyError> {                    Ok(($(self.$n.extract()?,)+))                }            }        )+    }} tuple_impls! {    (0 T0)    (0 T0, 1 T1)    (0 T0, 1 T1, 2 T2)    (0 T0, 1 T1, 2 T2, 3 T3)} /// Type-erased [`TemplateProperty`].

Lines 1–500 of 1170.