2022-11-26 23:57:50 +00:00
|
|
|
// Copyright 2020 The Jujutsu Authors
|
2020-12-12 08:00:42 +00:00
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
|
2023-02-02 08:57:55 +00:00
|
|
|
use std::{error, fmt};
|
|
|
|
|
2023-01-28 11:09:13 +00:00
|
|
|
use itertools::Itertools as _;
|
2023-01-02 23:34:54 +00:00
|
|
|
use jujutsu_lib::backend::{Signature, Timestamp};
|
2021-05-15 16:16:31 +00:00
|
|
|
use jujutsu_lib::commit::Commit;
|
2022-02-02 18:14:03 +00:00
|
|
|
use jujutsu_lib::op_store::WorkspaceId;
|
2021-05-15 16:16:31 +00:00
|
|
|
use jujutsu_lib::repo::RepoRef;
|
2023-01-31 03:48:38 +00:00
|
|
|
use jujutsu_lib::rewrite;
|
2021-03-14 17:46:35 +00:00
|
|
|
use pest::iterators::{Pair, Pairs};
|
2020-12-12 08:00:42 +00:00
|
|
|
use pest::Parser;
|
2022-09-22 04:52:04 +00:00
|
|
|
use pest_derive::Parser;
|
2023-02-02 08:57:55 +00:00
|
|
|
use thiserror::Error;
|
2020-12-12 08:00:42 +00:00
|
|
|
|
|
|
|
use crate::templater::{
|
2023-01-26 11:00:52 +00:00
|
|
|
BranchProperty, CommitOrChangeId, ConditionalTemplate, FormattablePropertyTemplate,
|
|
|
|
GitHeadProperty, GitRefsProperty, IdWithHighlightedPrefix, IsWorkingCopyProperty,
|
|
|
|
LabelTemplate, ListTemplate, Literal, PlainTextFormattedProperty, TagProperty, Template,
|
|
|
|
TemplateFunction, TemplateProperty, TemplatePropertyFn, WorkingCopiesProperty,
|
2020-12-12 08:00:42 +00:00
|
|
|
};
|
2023-01-31 03:48:38 +00:00
|
|
|
use crate::{cli_util, time_util};
|
2020-12-12 08:00:42 +00:00
|
|
|
|
|
|
|
#[derive(Parser)]
|
|
|
|
#[grammar = "template.pest"]
|
|
|
|
pub struct TemplateParser;
|
|
|
|
|
2023-02-02 08:57:55 +00:00
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct TemplateParseError {
|
|
|
|
kind: TemplateParseErrorKind,
|
|
|
|
pest_error: Box<pest::error::Error<Rule>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Eq, Error, PartialEq)]
|
|
|
|
pub enum TemplateParseErrorKind {
|
|
|
|
#[error("Syntax error")]
|
|
|
|
SyntaxError,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<pest::error::Error<Rule>> for TemplateParseError {
|
|
|
|
fn from(err: pest::error::Error<Rule>) -> Self {
|
|
|
|
TemplateParseError {
|
|
|
|
kind: TemplateParseErrorKind::SyntaxError,
|
|
|
|
pest_error: Box::new(err),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for TemplateParseError {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
self.pest_error.fmt(f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl error::Error for TemplateParseError {
|
|
|
|
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
|
|
|
match &self.kind {
|
|
|
|
// SyntaxError is a wrapper for pest::error::Error.
|
|
|
|
TemplateParseErrorKind::SyntaxError => Some(&self.pest_error as &dyn error::Error),
|
|
|
|
// Otherwise the kind represents this error.
|
|
|
|
// TODO: e => e.source(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-12 08:00:42 +00:00
|
|
|
fn parse_string_literal(pair: Pair<Rule>) -> String {
|
|
|
|
assert_eq!(pair.as_rule(), Rule::literal);
|
|
|
|
let mut result = String::new();
|
|
|
|
for part in pair.into_inner() {
|
|
|
|
match part.as_rule() {
|
|
|
|
Rule::raw_literal => {
|
|
|
|
result.push_str(part.as_str());
|
|
|
|
}
|
|
|
|
Rule::escape => match part.as_str().as_bytes()[1] as char {
|
|
|
|
'"' => result.push('"'),
|
|
|
|
'\\' => result.push('\\'),
|
|
|
|
'n' => result.push('\n'),
|
2022-12-15 02:30:06 +00:00
|
|
|
char => panic!("invalid escape: \\{char:?}"),
|
2020-12-12 08:00:42 +00:00
|
|
|
},
|
2022-12-15 02:30:06 +00:00
|
|
|
_ => panic!("unexpected part of string: {part:?}"),
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
result
|
|
|
|
}
|
|
|
|
|
2023-01-07 02:04:08 +00:00
|
|
|
enum Property<'a, I> {
|
templater: turn output parameter of TemplateProperty into associated type
When implementing FormattablePropertyTemplate, I tried a generic 'property: P'
first, and I couldn't figure out how to constrain the output type.
impl<C, O, P> Template<C> for FormattablePropertyTemplate<P>
where
P: TemplateProperty<C, O>, // 'O' isn't constrained by type
O: Template<()>,
According to the book, the problem is that we can add multiple implementations
of 'TemplateProperty<C, *>'. Since TemplateProperty is basically a function
to extract data from 'C', I think the output parameter shouldn't be freely
chosen.
https://doc.rust-lang.org/book/ch19-03-advanced-traits.html
With this change, I can express the type constraint as follows:
impl<C, P> Template<C> for FormattablePropertyTemplate<P>
where
P: TemplateProperty<C>,
P::Output: Template<()>,
2023-01-23 06:26:27 +00:00
|
|
|
String(Box<dyn TemplateProperty<I, Output = String> + 'a>),
|
|
|
|
Boolean(Box<dyn TemplateProperty<I, Output = bool> + 'a>),
|
2023-01-23 06:51:01 +00:00
|
|
|
CommitOrChangeId(Box<dyn TemplateProperty<I, Output = CommitOrChangeId<'a>> + 'a>),
|
2023-01-31 04:41:50 +00:00
|
|
|
IdWithHighlightedPrefix(Box<dyn TemplateProperty<I, Output = IdWithHighlightedPrefix> + 'a>),
|
templater: turn output parameter of TemplateProperty into associated type
When implementing FormattablePropertyTemplate, I tried a generic 'property: P'
first, and I couldn't figure out how to constrain the output type.
impl<C, O, P> Template<C> for FormattablePropertyTemplate<P>
where
P: TemplateProperty<C, O>, // 'O' isn't constrained by type
O: Template<()>,
According to the book, the problem is that we can add multiple implementations
of 'TemplateProperty<C, *>'. Since TemplateProperty is basically a function
to extract data from 'C', I think the output parameter shouldn't be freely
chosen.
https://doc.rust-lang.org/book/ch19-03-advanced-traits.html
With this change, I can express the type constraint as follows:
impl<C, P> Template<C> for FormattablePropertyTemplate<P>
where
P: TemplateProperty<C>,
P::Output: Template<()>,
2023-01-23 06:26:27 +00:00
|
|
|
Signature(Box<dyn TemplateProperty<I, Output = Signature> + 'a>),
|
|
|
|
Timestamp(Box<dyn TemplateProperty<I, Output = Timestamp> + 'a>),
|
2023-01-07 02:04:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, I: 'a> Property<'a, I> {
|
templater: turn output parameter of TemplateProperty into associated type
When implementing FormattablePropertyTemplate, I tried a generic 'property: P'
first, and I couldn't figure out how to constrain the output type.
impl<C, O, P> Template<C> for FormattablePropertyTemplate<P>
where
P: TemplateProperty<C, O>, // 'O' isn't constrained by type
O: Template<()>,
According to the book, the problem is that we can add multiple implementations
of 'TemplateProperty<C, *>'. Since TemplateProperty is basically a function
to extract data from 'C', I think the output parameter shouldn't be freely
chosen.
https://doc.rust-lang.org/book/ch19-03-advanced-traits.html
With this change, I can express the type constraint as follows:
impl<C, P> Template<C> for FormattablePropertyTemplate<P>
where
P: TemplateProperty<C>,
P::Output: Template<()>,
2023-01-23 06:26:27 +00:00
|
|
|
fn after<C: 'a>(self, first: Box<dyn TemplateProperty<C, Output = I> + 'a>) -> Property<'a, C> {
|
2023-01-23 03:22:32 +00:00
|
|
|
fn chain<'a, C: 'a, I: 'a, O: 'a>(
|
templater: turn output parameter of TemplateProperty into associated type
When implementing FormattablePropertyTemplate, I tried a generic 'property: P'
first, and I couldn't figure out how to constrain the output type.
impl<C, O, P> Template<C> for FormattablePropertyTemplate<P>
where
P: TemplateProperty<C, O>, // 'O' isn't constrained by type
O: Template<()>,
According to the book, the problem is that we can add multiple implementations
of 'TemplateProperty<C, *>'. Since TemplateProperty is basically a function
to extract data from 'C', I think the output parameter shouldn't be freely
chosen.
https://doc.rust-lang.org/book/ch19-03-advanced-traits.html
With this change, I can express the type constraint as follows:
impl<C, P> Template<C> for FormattablePropertyTemplate<P>
where
P: TemplateProperty<C>,
P::Output: Template<()>,
2023-01-23 06:26:27 +00:00
|
|
|
first: Box<dyn TemplateProperty<C, Output = I> + 'a>,
|
|
|
|
second: Box<dyn TemplateProperty<I, Output = O> + 'a>,
|
|
|
|
) -> Box<dyn TemplateProperty<C, Output = O> + 'a> {
|
2023-01-23 10:42:57 +00:00
|
|
|
Box::new(TemplateFunction::new(first, move |value| {
|
|
|
|
second.extract(&value)
|
|
|
|
}))
|
2023-01-23 03:22:32 +00:00
|
|
|
}
|
|
|
|
match self {
|
|
|
|
Property::String(property) => Property::String(chain(first, property)),
|
|
|
|
Property::Boolean(property) => Property::Boolean(chain(first, property)),
|
2023-01-23 06:51:01 +00:00
|
|
|
Property::CommitOrChangeId(property) => {
|
|
|
|
Property::CommitOrChangeId(chain(first, property))
|
2023-01-23 03:22:32 +00:00
|
|
|
}
|
2023-01-31 04:41:50 +00:00
|
|
|
Property::IdWithHighlightedPrefix(property) => {
|
|
|
|
Property::IdWithHighlightedPrefix(chain(first, property))
|
|
|
|
}
|
2023-01-23 03:22:32 +00:00
|
|
|
Property::Signature(property) => Property::Signature(chain(first, property)),
|
|
|
|
Property::Timestamp(property) => Property::Timestamp(chain(first, property)),
|
2023-01-07 02:04:08 +00:00
|
|
|
}
|
|
|
|
}
|
2023-01-22 23:02:46 +00:00
|
|
|
|
2023-01-30 10:52:41 +00:00
|
|
|
fn try_into_boolean(self) -> Option<Box<dyn TemplateProperty<I, Output = bool> + 'a>> {
|
|
|
|
match self {
|
|
|
|
Property::String(property) => {
|
|
|
|
Some(Box::new(TemplateFunction::new(property, |s| !s.is_empty())))
|
|
|
|
}
|
|
|
|
Property::Boolean(property) => Some(property),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-26 11:20:43 +00:00
|
|
|
fn into_plain_text(self) -> Box<dyn TemplateProperty<I, Output = String> + 'a> {
|
|
|
|
match self {
|
|
|
|
Property::String(property) => property,
|
|
|
|
_ => Box::new(PlainTextFormattedProperty::new(self.into_template())),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-22 23:02:46 +00:00
|
|
|
fn into_template(self) -> Box<dyn Template<I> + 'a> {
|
|
|
|
fn wrap<'a, I: 'a, O: Template<()> + 'a>(
|
templater: turn output parameter of TemplateProperty into associated type
When implementing FormattablePropertyTemplate, I tried a generic 'property: P'
first, and I couldn't figure out how to constrain the output type.
impl<C, O, P> Template<C> for FormattablePropertyTemplate<P>
where
P: TemplateProperty<C, O>, // 'O' isn't constrained by type
O: Template<()>,
According to the book, the problem is that we can add multiple implementations
of 'TemplateProperty<C, *>'. Since TemplateProperty is basically a function
to extract data from 'C', I think the output parameter shouldn't be freely
chosen.
https://doc.rust-lang.org/book/ch19-03-advanced-traits.html
With this change, I can express the type constraint as follows:
impl<C, P> Template<C> for FormattablePropertyTemplate<P>
where
P: TemplateProperty<C>,
P::Output: Template<()>,
2023-01-23 06:26:27 +00:00
|
|
|
property: Box<dyn TemplateProperty<I, Output = O> + 'a>,
|
2023-01-22 23:02:46 +00:00
|
|
|
) -> Box<dyn Template<I> + 'a> {
|
|
|
|
Box::new(FormattablePropertyTemplate::new(property))
|
|
|
|
}
|
|
|
|
match self {
|
|
|
|
Property::String(property) => wrap(property),
|
|
|
|
Property::Boolean(property) => wrap(property),
|
2023-01-23 06:51:01 +00:00
|
|
|
Property::CommitOrChangeId(property) => wrap(property),
|
2023-01-31 04:41:50 +00:00
|
|
|
Property::IdWithHighlightedPrefix(property) => wrap(property),
|
2023-01-22 23:02:46 +00:00
|
|
|
Property::Signature(property) => wrap(property),
|
|
|
|
Property::Timestamp(property) => wrap(property),
|
|
|
|
}
|
|
|
|
}
|
2023-01-07 02:04:08 +00:00
|
|
|
}
|
|
|
|
|
2023-01-29 03:34:24 +00:00
|
|
|
struct PropertyAndLabels<'a, C>(Property<'a, C>, Vec<String>);
|
|
|
|
|
2023-01-31 02:42:26 +00:00
|
|
|
impl<'a, C: 'a> PropertyAndLabels<'a, C> {
|
|
|
|
fn into_template(self) -> Box<dyn Template<C> + 'a> {
|
|
|
|
let PropertyAndLabels(property, labels) = self;
|
|
|
|
if labels.is_empty() {
|
|
|
|
property.into_template()
|
|
|
|
} else {
|
2023-01-26 11:00:52 +00:00
|
|
|
Box::new(LabelTemplate::new(
|
|
|
|
property.into_template(),
|
|
|
|
Literal(labels),
|
|
|
|
))
|
2023-01-31 02:42:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-28 23:54:53 +00:00
|
|
|
enum Expression<'a, C> {
|
|
|
|
Property(PropertyAndLabels<'a, C>),
|
|
|
|
Template(Box<dyn Template<C> + 'a>),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, C: 'a> Expression<'a, C> {
|
2023-01-29 03:15:17 +00:00
|
|
|
fn try_into_boolean(self) -> Option<Box<dyn TemplateProperty<C, Output = bool> + 'a>> {
|
|
|
|
match self {
|
|
|
|
Expression::Property(PropertyAndLabels(property, _)) => property.try_into_boolean(),
|
|
|
|
Expression::Template(_) => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-26 11:20:43 +00:00
|
|
|
fn into_plain_text(self) -> Box<dyn TemplateProperty<C, Output = String> + 'a> {
|
|
|
|
match self {
|
|
|
|
Expression::Property(PropertyAndLabels(property, _)) => property.into_plain_text(),
|
|
|
|
Expression::Template(template) => Box::new(PlainTextFormattedProperty::new(template)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-28 23:54:53 +00:00
|
|
|
fn into_template(self) -> Box<dyn Template<C> + 'a> {
|
|
|
|
match self {
|
|
|
|
Expression::Property(property_labels) => property_labels.into_template(),
|
|
|
|
Expression::Template(template) => template,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-12 08:00:42 +00:00
|
|
|
fn parse_method_chain<'a, I: 'a>(
|
|
|
|
pair: Pair<Rule>,
|
2023-01-27 10:06:32 +00:00
|
|
|
input_property: PropertyAndLabels<'a, I>,
|
2023-01-07 20:00:51 +00:00
|
|
|
) -> PropertyAndLabels<'a, I> {
|
2023-01-27 10:06:32 +00:00
|
|
|
let PropertyAndLabels(mut property, mut labels) = input_property;
|
2020-12-12 08:00:42 +00:00
|
|
|
assert_eq!(pair.as_rule(), Rule::maybe_method);
|
2023-01-27 10:06:32 +00:00
|
|
|
for chain in pair.into_inner() {
|
|
|
|
assert_eq!(chain.as_rule(), Rule::function);
|
2023-01-29 03:39:51 +00:00
|
|
|
let (name, args) = {
|
|
|
|
let mut inner = chain.into_inner();
|
|
|
|
let name = inner.next().unwrap();
|
|
|
|
let args_pair = inner.next().unwrap();
|
|
|
|
assert_eq!(name.as_rule(), Rule::identifier);
|
|
|
|
assert_eq!(args_pair.as_rule(), Rule::function_arguments);
|
|
|
|
(name, args_pair.into_inner())
|
|
|
|
};
|
2023-01-27 10:06:32 +00:00
|
|
|
labels.push(name.as_str().to_owned());
|
|
|
|
property = match property {
|
|
|
|
Property::String(property) => parse_string_method(name, args).after(property),
|
|
|
|
Property::Boolean(property) => parse_boolean_method(name, args).after(property),
|
2023-01-23 06:51:01 +00:00
|
|
|
Property::CommitOrChangeId(property) => {
|
2023-01-27 10:06:32 +00:00
|
|
|
parse_commit_or_change_id_method(name, args).after(property)
|
2022-11-26 01:33:24 +00:00
|
|
|
}
|
2023-01-31 04:41:50 +00:00
|
|
|
Property::IdWithHighlightedPrefix(_property) => {
|
|
|
|
panic!("Commit or change ids with styled prefix don't have any methods")
|
|
|
|
}
|
2023-01-27 10:06:32 +00:00
|
|
|
Property::Signature(property) => parse_signature_method(name, args).after(property),
|
|
|
|
Property::Timestamp(property) => parse_timestamp_method(name, args).after(property),
|
2023-01-07 20:00:51 +00:00
|
|
|
};
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
2023-01-27 10:06:32 +00:00
|
|
|
PropertyAndLabels(property, labels)
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
2023-01-27 10:06:32 +00:00
|
|
|
fn parse_string_method<'a>(name: Pair<Rule>, _args: Pairs<Rule>) -> Property<'a, String> {
|
2023-01-29 08:46:43 +00:00
|
|
|
fn wrap_fn<'a, O>(
|
|
|
|
f: impl Fn(&String) -> O + 'a,
|
|
|
|
) -> Box<dyn TemplateProperty<String, Output = O> + 'a> {
|
|
|
|
Box::new(TemplatePropertyFn(f))
|
|
|
|
}
|
2020-12-12 08:00:42 +00:00
|
|
|
// TODO: validate arguments
|
2023-01-27 10:06:32 +00:00
|
|
|
match name.as_str() {
|
2023-01-31 09:49:28 +00:00
|
|
|
"first_line" => Property::String(wrap_fn(|s| {
|
|
|
|
s.lines().next().unwrap_or_default().to_string()
|
|
|
|
})),
|
2022-12-15 02:30:06 +00:00
|
|
|
name => panic!("no such string method: {name}"),
|
2023-01-27 10:06:32 +00:00
|
|
|
}
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
2023-01-27 10:06:32 +00:00
|
|
|
fn parse_boolean_method<'a>(name: Pair<Rule>, _args: Pairs<Rule>) -> Property<'a, bool> {
|
2020-12-12 08:00:42 +00:00
|
|
|
// TODO: validate arguments
|
|
|
|
panic!("no such boolean method: {}", name.as_str());
|
|
|
|
}
|
|
|
|
|
2023-01-21 05:38:42 +00:00
|
|
|
fn parse_commit_or_change_id_method<'a>(
|
2023-01-27 10:06:32 +00:00
|
|
|
name: Pair<Rule>,
|
|
|
|
_args: Pairs<Rule>,
|
|
|
|
) -> Property<'a, CommitOrChangeId<'a>> {
|
2023-01-29 08:46:43 +00:00
|
|
|
fn wrap_fn<'a, O>(
|
|
|
|
f: impl Fn(&CommitOrChangeId<'a>) -> O + 'a,
|
|
|
|
) -> Box<dyn TemplateProperty<CommitOrChangeId<'a>, Output = O> + 'a> {
|
|
|
|
Box::new(TemplatePropertyFn(f))
|
|
|
|
}
|
2020-12-12 08:00:42 +00:00
|
|
|
// TODO: validate arguments
|
2023-01-27 10:06:32 +00:00
|
|
|
match name.as_str() {
|
2023-01-29 08:46:43 +00:00
|
|
|
"short" => Property::String(wrap_fn(|id| id.short())),
|
2023-01-29 02:54:34 +00:00
|
|
|
"shortest_prefix_and_brackets" => {
|
2023-01-29 08:46:43 +00:00
|
|
|
Property::String(wrap_fn(|id| id.shortest_prefix_and_brackets()))
|
|
|
|
}
|
|
|
|
"shortest_styled_prefix" => {
|
|
|
|
Property::IdWithHighlightedPrefix(wrap_fn(|id| id.shortest_styled_prefix()))
|
2023-01-03 00:24:00 +00:00
|
|
|
}
|
2022-12-15 02:30:06 +00:00
|
|
|
name => panic!("no such commit ID method: {name}"),
|
2023-01-27 10:06:32 +00:00
|
|
|
}
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
2023-01-27 10:06:32 +00:00
|
|
|
fn parse_signature_method<'a>(name: Pair<Rule>, _args: Pairs<Rule>) -> Property<'a, Signature> {
|
2023-01-29 08:46:43 +00:00
|
|
|
fn wrap_fn<'a, O>(
|
|
|
|
f: impl Fn(&Signature) -> O + 'a,
|
|
|
|
) -> Box<dyn TemplateProperty<Signature, Output = O> + 'a> {
|
|
|
|
Box::new(TemplatePropertyFn(f))
|
|
|
|
}
|
2020-12-12 08:00:42 +00:00
|
|
|
// TODO: validate arguments
|
2023-01-27 10:06:32 +00:00
|
|
|
match name.as_str() {
|
2023-01-29 08:46:43 +00:00
|
|
|
"name" => Property::String(wrap_fn(|signature| signature.name.clone())),
|
|
|
|
"email" => Property::String(wrap_fn(|signature| signature.email.clone())),
|
|
|
|
"timestamp" => Property::Timestamp(wrap_fn(|signature| signature.timestamp.clone())),
|
2022-12-15 02:30:06 +00:00
|
|
|
name => panic!("no such commit ID method: {name}"),
|
2023-01-27 10:06:32 +00:00
|
|
|
}
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
2023-01-27 10:06:32 +00:00
|
|
|
fn parse_timestamp_method<'a>(name: Pair<Rule>, _args: Pairs<Rule>) -> Property<'a, Timestamp> {
|
2023-01-29 08:46:43 +00:00
|
|
|
fn wrap_fn<'a, O>(
|
|
|
|
f: impl Fn(&Timestamp) -> O + 'a,
|
|
|
|
) -> Box<dyn TemplateProperty<Timestamp, Output = O> + 'a> {
|
|
|
|
Box::new(TemplatePropertyFn(f))
|
|
|
|
}
|
2022-11-26 01:33:24 +00:00
|
|
|
// TODO: validate arguments
|
2023-01-27 10:06:32 +00:00
|
|
|
match name.as_str() {
|
2023-01-29 08:46:43 +00:00
|
|
|
"ago" => Property::String(wrap_fn(time_util::format_timestamp_relative_to_now)),
|
2022-12-15 02:30:06 +00:00
|
|
|
name => panic!("no such timestamp method: {name}"),
|
2023-01-27 10:06:32 +00:00
|
|
|
}
|
2022-11-26 01:33:24 +00:00
|
|
|
}
|
|
|
|
|
2022-02-02 18:14:03 +00:00
|
|
|
fn parse_commit_keyword<'a>(
|
|
|
|
repo: RepoRef<'a>,
|
|
|
|
workspace_id: &WorkspaceId,
|
|
|
|
pair: Pair<Rule>,
|
2023-01-07 19:41:30 +00:00
|
|
|
) -> PropertyAndLabels<'a, Commit> {
|
2023-01-31 03:48:38 +00:00
|
|
|
fn wrap_fn<'a, O>(
|
|
|
|
f: impl Fn(&Commit) -> O + 'a,
|
|
|
|
) -> Box<dyn TemplateProperty<Commit, Output = O> + 'a> {
|
|
|
|
Box::new(TemplatePropertyFn(f))
|
|
|
|
}
|
2020-12-12 08:00:42 +00:00
|
|
|
assert_eq!(pair.as_rule(), Rule::identifier);
|
|
|
|
let property = match pair.as_str() {
|
2023-01-31 03:48:38 +00:00
|
|
|
"description" => Property::String(wrap_fn(|commit| {
|
|
|
|
cli_util::complete_newline(commit.description())
|
|
|
|
})),
|
|
|
|
"change_id" => Property::CommitOrChangeId(wrap_fn(move |commit| {
|
|
|
|
CommitOrChangeId::new(repo, commit.change_id())
|
|
|
|
})),
|
|
|
|
"commit_id" => Property::CommitOrChangeId(wrap_fn(move |commit| {
|
|
|
|
CommitOrChangeId::new(repo, commit.id())
|
|
|
|
})),
|
|
|
|
"author" => Property::Signature(wrap_fn(|commit| commit.author().clone())),
|
|
|
|
"committer" => Property::Signature(wrap_fn(|commit| commit.committer().clone())),
|
2022-09-18 21:46:12 +00:00
|
|
|
"working_copies" => Property::String(Box::new(WorkingCopiesProperty { repo })),
|
|
|
|
"current_working_copy" => Property::Boolean(Box::new(IsWorkingCopyProperty {
|
2022-02-02 18:14:03 +00:00
|
|
|
repo,
|
|
|
|
workspace_id: workspace_id.clone(),
|
|
|
|
})),
|
2021-07-15 08:31:48 +00:00
|
|
|
"branches" => Property::String(Box::new(BranchProperty { repo })),
|
|
|
|
"tags" => Property::String(Box::new(TagProperty { repo })),
|
2021-01-03 08:26:57 +00:00
|
|
|
"git_refs" => Property::String(Box::new(GitRefsProperty { repo })),
|
2022-12-17 18:17:50 +00:00
|
|
|
"git_head" => Property::String(Box::new(GitHeadProperty::new(repo))),
|
2023-01-31 03:48:38 +00:00
|
|
|
"divergent" => Property::Boolean(wrap_fn(move |commit| {
|
|
|
|
// The given commit could be hidden in e.g. obslog.
|
|
|
|
let maybe_entries = repo.resolve_change_id(commit.change_id());
|
|
|
|
maybe_entries.map_or(0, |entries| entries.len()) > 1
|
|
|
|
})),
|
|
|
|
"conflict" => Property::Boolean(wrap_fn(|commit| commit.tree().has_conflict())),
|
|
|
|
"empty" => Property::Boolean(wrap_fn(move |commit| {
|
|
|
|
commit.tree().id() == rewrite::merge_commit_trees(repo, &commit.parents()).id()
|
|
|
|
})),
|
2022-12-15 02:30:06 +00:00
|
|
|
name => panic!("unexpected identifier: {name}"),
|
2020-12-12 08:00:42 +00:00
|
|
|
};
|
2023-01-07 19:41:30 +00:00
|
|
|
PropertyAndLabels(property, vec![pair.as_str().to_string()])
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
2022-02-02 18:14:03 +00:00
|
|
|
fn parse_commit_term<'a>(
|
|
|
|
repo: RepoRef<'a>,
|
|
|
|
workspace_id: &WorkspaceId,
|
|
|
|
pair: Pair<Rule>,
|
2023-01-28 23:54:53 +00:00
|
|
|
) -> Expression<'a, Commit> {
|
2020-12-12 08:00:42 +00:00
|
|
|
assert_eq!(pair.as_rule(), Rule::term);
|
2023-01-28 11:09:13 +00:00
|
|
|
let mut inner = pair.into_inner();
|
|
|
|
let expr = inner.next().unwrap();
|
|
|
|
let maybe_method = inner.next().unwrap();
|
|
|
|
assert!(inner.next().is_none());
|
|
|
|
match expr.as_rule() {
|
|
|
|
Rule::literal => {
|
|
|
|
let text = parse_string_literal(expr);
|
|
|
|
let term = PropertyAndLabels(Property::String(Box::new(Literal(text))), vec![]);
|
2023-01-28 23:54:53 +00:00
|
|
|
Expression::Property(parse_method_chain(maybe_method, term))
|
2023-01-28 11:09:13 +00:00
|
|
|
}
|
|
|
|
Rule::identifier => {
|
|
|
|
let term = parse_commit_keyword(repo, workspace_id, expr);
|
2023-01-28 23:54:53 +00:00
|
|
|
Expression::Property(parse_method_chain(maybe_method, term))
|
2023-01-28 11:09:13 +00:00
|
|
|
}
|
|
|
|
Rule::function => {
|
2023-01-29 03:39:51 +00:00
|
|
|
let (name, mut args) = {
|
|
|
|
let mut inner = expr.into_inner();
|
|
|
|
let name = inner.next().unwrap();
|
|
|
|
let args_pair = inner.next().unwrap();
|
|
|
|
assert_eq!(name.as_rule(), Rule::identifier);
|
|
|
|
assert_eq!(args_pair.as_rule(), Rule::function_arguments);
|
|
|
|
(name, args_pair.into_inner())
|
|
|
|
};
|
|
|
|
match name.as_str() {
|
2023-01-28 11:09:13 +00:00
|
|
|
"label" => {
|
2023-01-29 03:39:51 +00:00
|
|
|
let label_pair = args.next().unwrap();
|
2023-01-26 11:20:43 +00:00
|
|
|
let label_property = parse_commit_template_rule(repo, workspace_id, label_pair)
|
|
|
|
.into_plain_text();
|
2023-01-29 03:39:51 +00:00
|
|
|
let arg_template = match args.next() {
|
2023-01-28 11:09:13 +00:00
|
|
|
None => panic!("label() requires two arguments"),
|
|
|
|
Some(pair) => pair,
|
|
|
|
};
|
2023-01-29 03:39:51 +00:00
|
|
|
if args.next().is_some() {
|
2023-01-28 11:09:13 +00:00
|
|
|
panic!("label() accepts only two arguments")
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
2023-01-28 23:54:53 +00:00
|
|
|
let content = parse_commit_template_rule(repo, workspace_id, arg_template)
|
|
|
|
.into_template();
|
2023-01-26 11:00:52 +00:00
|
|
|
let labels = TemplateFunction::new(label_property, |s| {
|
|
|
|
s.split_whitespace().map(ToString::to_string).collect()
|
|
|
|
});
|
|
|
|
let template = Box::new(LabelTemplate::new(content, labels));
|
2023-01-28 23:54:53 +00:00
|
|
|
Expression::Template(template)
|
2023-01-28 11:09:13 +00:00
|
|
|
}
|
|
|
|
"if" => {
|
2023-01-29 03:39:51 +00:00
|
|
|
let condition_pair = args.next().unwrap();
|
2023-01-28 11:09:13 +00:00
|
|
|
let condition =
|
2023-01-29 03:15:17 +00:00
|
|
|
parse_commit_template_rule(repo, workspace_id, condition_pair.clone())
|
|
|
|
.try_into_boolean()
|
|
|
|
.unwrap_or_else(|| {
|
|
|
|
panic!("cannot yet use this as boolean: {condition_pair:?}")
|
|
|
|
});
|
2023-01-28 11:09:13 +00:00
|
|
|
|
2023-01-29 03:39:51 +00:00
|
|
|
let true_template = match args.next() {
|
2023-01-28 11:09:13 +00:00
|
|
|
None => panic!("if() requires at least two arguments"),
|
2023-01-28 23:54:53 +00:00
|
|
|
Some(pair) => {
|
|
|
|
parse_commit_template_rule(repo, workspace_id, pair).into_template()
|
|
|
|
}
|
2023-01-28 11:09:13 +00:00
|
|
|
};
|
2023-01-28 23:54:53 +00:00
|
|
|
let false_template = args.next().map(|pair| {
|
|
|
|
parse_commit_template_rule(repo, workspace_id, pair).into_template()
|
|
|
|
});
|
2023-01-29 03:39:51 +00:00
|
|
|
if args.next().is_some() {
|
2023-01-28 11:09:13 +00:00
|
|
|
panic!("if() accepts at most three arguments")
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
2023-01-28 23:54:53 +00:00
|
|
|
let template = Box::new(ConditionalTemplate::new(
|
2023-01-28 11:09:13 +00:00
|
|
|
condition,
|
|
|
|
true_template,
|
|
|
|
false_template,
|
2023-01-28 23:54:53 +00:00
|
|
|
));
|
|
|
|
Expression::Template(template)
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
2023-01-28 11:09:13 +00:00
|
|
|
name => panic!("function {name} not implemented"),
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
}
|
2023-01-28 11:09:13 +00:00
|
|
|
Rule::template => parse_commit_template_rule(repo, workspace_id, expr),
|
|
|
|
other => panic!("unexpected term: {other:?}"),
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-11 16:13:00 +00:00
|
|
|
fn parse_commit_template_rule<'a>(
|
|
|
|
repo: RepoRef<'a>,
|
2022-02-02 18:14:03 +00:00
|
|
|
workspace_id: &WorkspaceId,
|
2020-12-12 08:00:42 +00:00
|
|
|
pair: Pair<Rule>,
|
2023-01-28 23:54:53 +00:00
|
|
|
) -> Expression<'a, Commit> {
|
2023-01-28 11:09:13 +00:00
|
|
|
assert_eq!(pair.as_rule(), Rule::template);
|
|
|
|
let inner = pair.into_inner();
|
2023-01-28 23:54:53 +00:00
|
|
|
let mut expressions = inner
|
2023-01-28 11:09:13 +00:00
|
|
|
.map(|term| parse_commit_term(repo, workspace_id, term))
|
|
|
|
.collect_vec();
|
2023-01-28 23:54:53 +00:00
|
|
|
if expressions.len() == 1 {
|
|
|
|
expressions.pop().unwrap()
|
2023-01-29 03:59:46 +00:00
|
|
|
} else {
|
2023-01-28 23:54:53 +00:00
|
|
|
let templates = expressions.into_iter().map(|x| x.into_template()).collect();
|
|
|
|
Expression::Template(Box::new(ListTemplate(templates)))
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-11 16:13:00 +00:00
|
|
|
pub fn parse_commit_template<'a>(
|
|
|
|
repo: RepoRef<'a>,
|
2022-02-02 18:14:03 +00:00
|
|
|
workspace_id: &WorkspaceId,
|
2020-12-12 08:00:42 +00:00
|
|
|
template_text: &str,
|
2023-02-02 08:57:55 +00:00
|
|
|
) -> Result<Box<dyn Template<Commit> + 'a>, TemplateParseError> {
|
|
|
|
let mut pairs: Pairs<Rule> = TemplateParser::parse(Rule::program, template_text)?;
|
2020-12-12 08:00:42 +00:00
|
|
|
let first_pair = pairs.next().unwrap();
|
2023-01-29 03:59:46 +00:00
|
|
|
if first_pair.as_rule() == Rule::EOI {
|
2023-02-02 08:57:55 +00:00
|
|
|
Ok(Box::new(Literal(String::new())))
|
2023-01-29 03:59:46 +00:00
|
|
|
} else {
|
2023-02-02 08:57:55 +00:00
|
|
|
Ok(parse_commit_template_rule(repo, workspace_id, first_pair).into_template())
|
2023-01-29 03:59:46 +00:00
|
|
|
}
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|