forked from mirrors/jj
templater: pass diagnostics receiver around
This commit is contained in:
parent
8b1760ca5d
commit
11286b3072
8 changed files with 752 additions and 431 deletions
|
@ -122,7 +122,7 @@ impl CommitTemplateLanguageExtension for HexCounter {
|
|||
let mut table = CommitTemplateBuildFnTable::empty();
|
||||
table.commit_methods.insert(
|
||||
"has_most_digits",
|
||||
|language, _build_context, property, call| {
|
||||
|language, _diagnostics, _build_context, property, call| {
|
||||
call.expect_no_arguments()?;
|
||||
let most_digits = language
|
||||
.cache_extension::<MostDigitsInId>()
|
||||
|
@ -135,7 +135,7 @@ impl CommitTemplateLanguageExtension for HexCounter {
|
|||
);
|
||||
table.commit_methods.insert(
|
||||
"num_digits_in_id",
|
||||
|_language, _build_context, property, call| {
|
||||
|_language, _diagnostics, _build_context, property, call| {
|
||||
call.expect_no_arguments()?;
|
||||
Ok(L::wrap_integer(
|
||||
property.map(|commit| num_digits_in_id(commit.id())),
|
||||
|
@ -144,7 +144,7 @@ impl CommitTemplateLanguageExtension for HexCounter {
|
|||
);
|
||||
table.commit_methods.insert(
|
||||
"num_char_in_id",
|
||||
|_language, _build_context, property, call| {
|
||||
|_language, _diagnostics, _build_context, property, call| {
|
||||
let [string_arg] = call.expect_exact_arguments()?;
|
||||
let char_arg =
|
||||
template_parser::expect_string_literal_with(string_arg, |string, span| {
|
||||
|
|
|
@ -53,7 +53,7 @@ impl OperationTemplateLanguageExtension for HexCounter {
|
|||
let mut table = OperationTemplateBuildFnTable::empty();
|
||||
table.operation_methods.insert(
|
||||
"num_digits_in_id",
|
||||
|_language, _build_context, property, call| {
|
||||
|_language, _diagnostics, _build_context, property, call| {
|
||||
call.expect_no_arguments()?;
|
||||
Ok(L::wrap_integer(
|
||||
property.map(|operation| num_digits_in_id(operation.id())),
|
||||
|
@ -62,7 +62,7 @@ impl OperationTemplateLanguageExtension for HexCounter {
|
|||
);
|
||||
table.operation_methods.insert(
|
||||
"num_char_in_id",
|
||||
|_language, _build_context, property, call| {
|
||||
|_language, _diagnostics, _build_context, property, call| {
|
||||
let [string_arg] = call.expect_exact_arguments()?;
|
||||
let char_arg =
|
||||
template_parser::expect_string_literal_with(string_arg, |string, span| {
|
||||
|
|
|
@ -166,7 +166,7 @@ use crate::revset_util::RevsetExpressionEvaluator;
|
|||
use crate::template_builder;
|
||||
use crate::template_builder::TemplateLanguage;
|
||||
use crate::template_parser::TemplateAliasesMap;
|
||||
use crate::template_parser::TemplateParseResult;
|
||||
use crate::template_parser::TemplateDiagnostics;
|
||||
use crate::templater::PropertyPlaceholder;
|
||||
use crate::templater::TemplateRenderer;
|
||||
use crate::text_util;
|
||||
|
@ -343,13 +343,17 @@ impl CommandHelper {
|
|||
template_text: &str,
|
||||
wrap_self: impl Fn(PropertyPlaceholder<C>) -> L::Property,
|
||||
) -> Result<TemplateRenderer<'a, C>, CommandError> {
|
||||
let mut diagnostics = TemplateDiagnostics::new();
|
||||
let aliases = self.load_template_aliases(ui)?;
|
||||
Ok(template_builder::parse(
|
||||
let template = template_builder::parse(
|
||||
language,
|
||||
&mut diagnostics,
|
||||
template_text,
|
||||
&aliases,
|
||||
wrap_self,
|
||||
)?)
|
||||
)?;
|
||||
print_parse_diagnostics(ui, "In template expression", &diagnostics)?;
|
||||
Ok(template)
|
||||
}
|
||||
|
||||
pub fn workspace_loader(&self) -> Result<&dyn WorkspaceLoader, CommandError> {
|
||||
|
@ -780,13 +784,21 @@ impl WorkspaceCommandEnvironment {
|
|||
/// be one of the `L::wrap_*()` functions.
|
||||
pub fn parse_template<'a, C: Clone + 'a, L: TemplateLanguage<'a> + ?Sized>(
|
||||
&self,
|
||||
_ui: &Ui,
|
||||
ui: &Ui,
|
||||
language: &L,
|
||||
template_text: &str,
|
||||
wrap_self: impl Fn(PropertyPlaceholder<C>) -> L::Property,
|
||||
) -> TemplateParseResult<TemplateRenderer<'a, C>> {
|
||||
let aliases = &self.template_aliases_map;
|
||||
template_builder::parse(language, template_text, aliases, wrap_self)
|
||||
) -> Result<TemplateRenderer<'a, C>, CommandError> {
|
||||
let mut diagnostics = TemplateDiagnostics::new();
|
||||
let template = template_builder::parse(
|
||||
language,
|
||||
&mut diagnostics,
|
||||
template_text,
|
||||
&self.template_aliases_map,
|
||||
wrap_self,
|
||||
)?;
|
||||
print_parse_diagnostics(ui, "In template expression", &diagnostics)?;
|
||||
Ok(template)
|
||||
}
|
||||
|
||||
/// Creates commit template language environment for this workspace and the
|
||||
|
@ -1392,7 +1404,7 @@ impl WorkspaceCommandHelper {
|
|||
language: &L,
|
||||
template_text: &str,
|
||||
wrap_self: impl Fn(PropertyPlaceholder<C>) -> L::Property,
|
||||
) -> TemplateParseResult<TemplateRenderer<'a, C>> {
|
||||
) -> Result<TemplateRenderer<'a, C>, CommandError> {
|
||||
self.env
|
||||
.parse_template(ui, language, template_text, wrap_self)
|
||||
}
|
||||
|
@ -1404,8 +1416,13 @@ impl WorkspaceCommandHelper {
|
|||
template_text: &str,
|
||||
wrap_self: impl Fn(PropertyPlaceholder<C>) -> L::Property,
|
||||
) -> TemplateRenderer<'a, C> {
|
||||
let aliases = &self.env.template_aliases_map;
|
||||
template_builder::parse(language, template_text, aliases, wrap_self)
|
||||
template_builder::parse(
|
||||
language,
|
||||
&mut TemplateDiagnostics::new(),
|
||||
template_text,
|
||||
&self.env.template_aliases_map,
|
||||
wrap_self,
|
||||
)
|
||||
.expect("parse error should be confined by WorkspaceCommandHelper::new()")
|
||||
}
|
||||
|
||||
|
@ -1414,7 +1431,7 @@ impl WorkspaceCommandHelper {
|
|||
&self,
|
||||
ui: &Ui,
|
||||
template_text: &str,
|
||||
) -> TemplateParseResult<TemplateRenderer<'_, Commit>> {
|
||||
) -> Result<TemplateRenderer<'_, Commit>, CommandError> {
|
||||
let language = self.commit_template_language();
|
||||
self.parse_template(
|
||||
ui,
|
||||
|
@ -1429,7 +1446,7 @@ impl WorkspaceCommandHelper {
|
|||
&self,
|
||||
ui: &Ui,
|
||||
template_text: &str,
|
||||
) -> TemplateParseResult<TemplateRenderer<'_, Operation>> {
|
||||
) -> Result<TemplateRenderer<'_, Operation>, CommandError> {
|
||||
let language = self.operation_template_language();
|
||||
self.parse_template(
|
||||
ui,
|
||||
|
@ -2056,7 +2073,7 @@ impl WorkspaceCommandTransaction<'_> {
|
|||
&self,
|
||||
ui: &Ui,
|
||||
template_text: &str,
|
||||
) -> TemplateParseResult<TemplateRenderer<'_, Commit>> {
|
||||
) -> Result<TemplateRenderer<'_, Commit>, CommandError> {
|
||||
let language = self.commit_template_language();
|
||||
self.helper.env.parse_template(
|
||||
ui,
|
||||
|
|
|
@ -64,6 +64,7 @@ use crate::template_builder::TemplateLanguage;
|
|||
use crate::template_parser;
|
||||
use crate::template_parser::ExpressionNode;
|
||||
use crate::template_parser::FunctionCallNode;
|
||||
use crate::template_parser::TemplateDiagnostics;
|
||||
use crate::template_parser::TemplateParseError;
|
||||
use crate::template_parser::TemplateParseResult;
|
||||
use crate::templater;
|
||||
|
@ -142,15 +143,17 @@ impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> {
|
|||
|
||||
fn build_function(
|
||||
&self,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<Self::Property>,
|
||||
function: &FunctionCallNode,
|
||||
) -> TemplateParseResult<Self::Property> {
|
||||
let table = &self.build_fn_table.core;
|
||||
table.build_function(self, build_ctx, function)
|
||||
table.build_function(self, diagnostics, build_ctx, function)
|
||||
}
|
||||
|
||||
fn build_method(
|
||||
&self,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<Self::Property>,
|
||||
property: Self::Property,
|
||||
function: &FunctionCallNode,
|
||||
|
@ -159,24 +162,31 @@ impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> {
|
|||
match property {
|
||||
CommitTemplatePropertyKind::Core(property) => {
|
||||
let table = &self.build_fn_table.core;
|
||||
table.build_method(self, build_ctx, property, function)
|
||||
table.build_method(self, diagnostics, build_ctx, property, function)
|
||||
}
|
||||
CommitTemplatePropertyKind::Commit(property) => {
|
||||
let table = &self.build_fn_table.commit_methods;
|
||||
let build = template_parser::lookup_method(type_name, table, function)?;
|
||||
build(self, build_ctx, property, function)
|
||||
build(self, diagnostics, build_ctx, property, function)
|
||||
}
|
||||
CommitTemplatePropertyKind::CommitOpt(property) => {
|
||||
let type_name = "Commit";
|
||||
let table = &self.build_fn_table.commit_methods;
|
||||
let build = template_parser::lookup_method(type_name, table, function)?;
|
||||
let inner_property = property.try_unwrap(type_name);
|
||||
build(self, build_ctx, Box::new(inner_property), function)
|
||||
build(
|
||||
self,
|
||||
diagnostics,
|
||||
build_ctx,
|
||||
Box::new(inner_property),
|
||||
function,
|
||||
)
|
||||
}
|
||||
CommitTemplatePropertyKind::CommitList(property) => {
|
||||
// TODO: migrate to table?
|
||||
template_builder::build_unformattable_list_method(
|
||||
self,
|
||||
diagnostics,
|
||||
build_ctx,
|
||||
property,
|
||||
function,
|
||||
|
@ -186,19 +196,26 @@ impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> {
|
|||
CommitTemplatePropertyKind::RefName(property) => {
|
||||
let table = &self.build_fn_table.ref_name_methods;
|
||||
let build = template_parser::lookup_method(type_name, table, function)?;
|
||||
build(self, build_ctx, property, function)
|
||||
build(self, diagnostics, build_ctx, property, function)
|
||||
}
|
||||
CommitTemplatePropertyKind::RefNameOpt(property) => {
|
||||
let type_name = "RefName";
|
||||
let table = &self.build_fn_table.ref_name_methods;
|
||||
let build = template_parser::lookup_method(type_name, table, function)?;
|
||||
let inner_property = property.try_unwrap(type_name);
|
||||
build(self, build_ctx, Box::new(inner_property), function)
|
||||
build(
|
||||
self,
|
||||
diagnostics,
|
||||
build_ctx,
|
||||
Box::new(inner_property),
|
||||
function,
|
||||
)
|
||||
}
|
||||
CommitTemplatePropertyKind::RefNameList(property) => {
|
||||
// TODO: migrate to table?
|
||||
template_builder::build_formattable_list_method(
|
||||
self,
|
||||
diagnostics,
|
||||
build_ctx,
|
||||
property,
|
||||
function,
|
||||
|
@ -208,17 +225,17 @@ impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> {
|
|||
CommitTemplatePropertyKind::CommitOrChangeId(property) => {
|
||||
let table = &self.build_fn_table.commit_or_change_id_methods;
|
||||
let build = template_parser::lookup_method(type_name, table, function)?;
|
||||
build(self, build_ctx, property, function)
|
||||
build(self, diagnostics, build_ctx, property, function)
|
||||
}
|
||||
CommitTemplatePropertyKind::ShortestIdPrefix(property) => {
|
||||
let table = &self.build_fn_table.shortest_id_prefix_methods;
|
||||
let build = template_parser::lookup_method(type_name, table, function)?;
|
||||
build(self, build_ctx, property, function)
|
||||
build(self, diagnostics, build_ctx, property, function)
|
||||
}
|
||||
CommitTemplatePropertyKind::TreeDiff(property) => {
|
||||
let table = &self.build_fn_table.tree_diff_methods;
|
||||
let build = template_parser::lookup_method(type_name, table, function)?;
|
||||
build(self, build_ctx, property, function)
|
||||
build(self, diagnostics, build_ctx, property, function)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -500,7 +517,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
|
|||
let mut map = CommitTemplateBuildMethodFnMap::<Commit>::new();
|
||||
map.insert(
|
||||
"description",
|
||||
|_language, _build_ctx, self_property, function| {
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property =
|
||||
self_property.map(|commit| text_util::complete_newline(commit.description()));
|
||||
|
@ -509,7 +526,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
|
|||
);
|
||||
map.insert(
|
||||
"change_id",
|
||||
|_language, _build_ctx, self_property, function| {
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property =
|
||||
self_property.map(|commit| CommitOrChangeId::Change(commit.change_id().to_owned()));
|
||||
|
@ -518,7 +535,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
|
|||
);
|
||||
map.insert(
|
||||
"commit_id",
|
||||
|_language, _build_ctx, self_property, function| {
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property =
|
||||
self_property.map(|commit| CommitOrChangeId::Commit(commit.id().to_owned()));
|
||||
|
@ -527,7 +544,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
|
|||
);
|
||||
map.insert(
|
||||
"parents",
|
||||
|_language, _build_ctx, self_property, function| {
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property =
|
||||
self_property.and_then(|commit| Ok(commit.parents().try_collect()?));
|
||||
|
@ -536,7 +553,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
|
|||
);
|
||||
map.insert(
|
||||
"author",
|
||||
|_language, _build_ctx, self_property, function| {
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|commit| commit.author().clone());
|
||||
Ok(L::wrap_signature(out_property))
|
||||
|
@ -544,21 +561,24 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
|
|||
);
|
||||
map.insert(
|
||||
"committer",
|
||||
|_language, _build_ctx, self_property, function| {
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|commit| commit.committer().clone());
|
||||
Ok(L::wrap_signature(out_property))
|
||||
},
|
||||
);
|
||||
map.insert("mine", |language, _build_ctx, self_property, function| {
|
||||
map.insert(
|
||||
"mine",
|
||||
|language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let user_email = language.revset_parse_context.user_email().to_owned();
|
||||
let out_property = self_property.map(move |commit| commit.author().email == user_email);
|
||||
Ok(L::wrap_boolean(out_property))
|
||||
});
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"working_copies",
|
||||
|language, _build_ctx, self_property, function| {
|
||||
|language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let repo = language.repo;
|
||||
let out_property = self_property.map(|commit| extract_working_copies(repo, &commit));
|
||||
|
@ -567,7 +587,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
|
|||
);
|
||||
map.insert(
|
||||
"current_working_copy",
|
||||
|language, _build_ctx, self_property, function| {
|
||||
|language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let repo = language.repo;
|
||||
let workspace_id = language.workspace_id.clone();
|
||||
|
@ -579,7 +599,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
|
|||
);
|
||||
map.insert(
|
||||
"bookmarks",
|
||||
|language, _build_ctx, self_property, function| {
|
||||
|language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let index = language
|
||||
.keyword_cache
|
||||
|
@ -598,7 +618,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
|
|||
);
|
||||
map.insert(
|
||||
"local_bookmarks",
|
||||
|language, _build_ctx, self_property, function| {
|
||||
|language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let index = language
|
||||
.keyword_cache
|
||||
|
@ -617,7 +637,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
|
|||
);
|
||||
map.insert(
|
||||
"remote_bookmarks",
|
||||
|language, _build_ctx, self_property, function| {
|
||||
|language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let index = language
|
||||
.keyword_cache
|
||||
|
@ -639,15 +659,18 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
|
|||
map.insert("local_branches", map["local_bookmarks"]);
|
||||
map.insert("remote_branches", map["remote_bookmarks"]);
|
||||
|
||||
map.insert("tags", |language, _build_ctx, self_property, function| {
|
||||
map.insert(
|
||||
"tags",
|
||||
|language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let index = language.keyword_cache.tags_index(language.repo).clone();
|
||||
let out_property = self_property.map(move |commit| index.get(commit.id()).to_vec());
|
||||
Ok(L::wrap_ref_name_list(out_property))
|
||||
});
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"git_refs",
|
||||
|language, _build_ctx, self_property, function| {
|
||||
|language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let index = language.keyword_cache.git_refs_index(language.repo).clone();
|
||||
let out_property = self_property.map(move |commit| index.get(commit.id()).to_vec());
|
||||
|
@ -656,7 +679,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
|
|||
);
|
||||
map.insert(
|
||||
"git_head",
|
||||
|language, _build_ctx, self_property, function| {
|
||||
|language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let repo = language.repo;
|
||||
let out_property = self_property.map(|commit| extract_git_head(repo, &commit));
|
||||
|
@ -665,7 +688,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
|
|||
);
|
||||
map.insert(
|
||||
"divergent",
|
||||
|language, _build_ctx, self_property, function| {
|
||||
|language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let repo = language.repo;
|
||||
let out_property = self_property.map(|commit| {
|
||||
|
@ -676,7 +699,9 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
|
|||
Ok(L::wrap_boolean(out_property))
|
||||
},
|
||||
);
|
||||
map.insert("hidden", |language, _build_ctx, self_property, function| {
|
||||
map.insert(
|
||||
"hidden",
|
||||
|language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let repo = language.repo;
|
||||
let out_property = self_property.map(|commit| {
|
||||
|
@ -684,10 +709,11 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
|
|||
maybe_entries.map_or(true, |entries| !entries.contains(commit.id()))
|
||||
});
|
||||
Ok(L::wrap_boolean(out_property))
|
||||
});
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"immutable",
|
||||
|language, _build_ctx, self_property, function| {
|
||||
|language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let is_immutable = language
|
||||
.keyword_cache
|
||||
|
@ -699,12 +725,12 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
|
|||
);
|
||||
map.insert(
|
||||
"contained_in",
|
||||
|language, _build_ctx, self_property, function| {
|
||||
|language, diagnostics, _build_ctx, self_property, function| {
|
||||
let [revset_node] = function.expect_exact_arguments()?;
|
||||
|
||||
let is_contained =
|
||||
template_parser::expect_string_literal_with(revset_node, |revset, span| {
|
||||
Ok(evaluate_user_revset(language, span, revset)?.containing_fn())
|
||||
Ok(evaluate_user_revset(language, diagnostics, span, revset)?.containing_fn())
|
||||
})?;
|
||||
|
||||
let out_property = self_property.map(move |commit| is_contained(commit.id()));
|
||||
|
@ -713,22 +739,27 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
|
|||
);
|
||||
map.insert(
|
||||
"conflict",
|
||||
|_language, _build_ctx, self_property, function| {
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.and_then(|commit| Ok(commit.has_conflict()?));
|
||||
Ok(L::wrap_boolean(out_property))
|
||||
},
|
||||
);
|
||||
map.insert("empty", |language, _build_ctx, self_property, function| {
|
||||
map.insert(
|
||||
"empty",
|
||||
|language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let repo = language.repo;
|
||||
let out_property = self_property.and_then(|commit| Ok(commit.is_empty(repo)?));
|
||||
Ok(L::wrap_boolean(out_property))
|
||||
});
|
||||
map.insert("diff", |language, _build_ctx, self_property, function| {
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"diff",
|
||||
|language, diagnostics, _build_ctx, self_property, function| {
|
||||
let ([], [files_node]) = function.expect_arguments()?;
|
||||
let files = if let Some(node) = files_node {
|
||||
expect_fileset_literal(node, language.path_converter)?
|
||||
expect_fileset_literal(diagnostics, node, language.path_converter)?
|
||||
} else {
|
||||
// TODO: defaults to CLI path arguments?
|
||||
// https://github.com/martinvonz/jj/issues/2933#issuecomment-1925870731
|
||||
|
@ -739,13 +770,18 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
|
|||
let out_property = self_property
|
||||
.and_then(move |commit| Ok(TreeDiff::from_commit(repo, &commit, matcher.clone())?));
|
||||
Ok(L::wrap_tree_diff(out_property))
|
||||
});
|
||||
map.insert("root", |language, _build_ctx, self_property, function| {
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"root",
|
||||
|language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let repo = language.repo;
|
||||
let out_property = self_property.map(|commit| commit.id() == repo.store().root_commit_id());
|
||||
let out_property =
|
||||
self_property.map(|commit| commit.id() == repo.store().root_commit_id());
|
||||
Ok(L::wrap_boolean(out_property))
|
||||
});
|
||||
},
|
||||
);
|
||||
map
|
||||
}
|
||||
|
||||
|
@ -765,14 +801,20 @@ fn extract_working_copies(repo: &dyn Repo, commit: &Commit) -> String {
|
|||
}
|
||||
|
||||
fn expect_fileset_literal(
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
node: &ExpressionNode,
|
||||
path_converter: &RepoPathUiConverter,
|
||||
) -> Result<FilesetExpression, TemplateParseError> {
|
||||
template_parser::expect_string_literal_with(node, |text, span| {
|
||||
let mut inner_diagnostics = FilesetDiagnostics::new(); // TODO
|
||||
let mut inner_diagnostics = FilesetDiagnostics::new();
|
||||
let expression =
|
||||
fileset::parse(&mut inner_diagnostics, text, path_converter).map_err(|err| {
|
||||
TemplateParseError::expression("In fileset expression", span).with_source(err)
|
||||
})
|
||||
})?;
|
||||
diagnostics.extend_with(inner_diagnostics, |diag| {
|
||||
TemplateParseError::expression("In fileset expression", span).with_source(diag)
|
||||
});
|
||||
Ok(expression)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -797,16 +839,20 @@ fn evaluate_revset_expression<'repo>(
|
|||
|
||||
fn evaluate_user_revset<'repo>(
|
||||
language: &CommitTemplateLanguage<'repo>,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
span: pest::Span<'_>,
|
||||
revset: &str,
|
||||
) -> Result<Box<dyn Revset + 'repo>, TemplateParseError> {
|
||||
let mut inner_diagnostics = RevsetDiagnostics::new(); // TODO
|
||||
let mut inner_diagnostics = RevsetDiagnostics::new();
|
||||
let (expression, modifier) = revset::parse_with_modifier(
|
||||
&mut inner_diagnostics,
|
||||
revset,
|
||||
&language.revset_parse_context,
|
||||
)
|
||||
.map_err(|err| TemplateParseError::expression("In revset expression", span).with_source(err))?;
|
||||
diagnostics.extend_with(inner_diagnostics, |diag| {
|
||||
TemplateParseError::expression("In revset expression", span).with_source(diag)
|
||||
});
|
||||
let (None | Some(RevsetModifier::All)) = modifier;
|
||||
|
||||
evaluate_revset_expression(language, span, expression)
|
||||
|
@ -1003,14 +1049,17 @@ fn builtin_ref_name_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Rc
|
|||
// Not using maplit::hashmap!{} or custom declarative macro here because
|
||||
// code completion inside macro is quite restricted.
|
||||
let mut map = CommitTemplateBuildMethodFnMap::<Rc<RefName>>::new();
|
||||
map.insert("name", |_language, _build_ctx, self_property, function| {
|
||||
map.insert(
|
||||
"name",
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|ref_name| ref_name.name.clone());
|
||||
Ok(L::wrap_string(out_property))
|
||||
});
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"remote",
|
||||
|_language, _build_ctx, self_property, function| {
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property =
|
||||
self_property.map(|ref_name| ref_name.remote.clone().unwrap_or_default());
|
||||
|
@ -1019,7 +1068,7 @@ fn builtin_ref_name_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Rc
|
|||
);
|
||||
map.insert(
|
||||
"present",
|
||||
|_language, _build_ctx, self_property, function| {
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|ref_name| ref_name.is_present());
|
||||
Ok(L::wrap_boolean(out_property))
|
||||
|
@ -1027,7 +1076,7 @@ fn builtin_ref_name_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Rc
|
|||
);
|
||||
map.insert(
|
||||
"conflict",
|
||||
|_language, _build_ctx, self_property, function| {
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|ref_name| ref_name.has_conflict());
|
||||
Ok(L::wrap_boolean(out_property))
|
||||
|
@ -1035,7 +1084,7 @@ fn builtin_ref_name_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Rc
|
|||
);
|
||||
map.insert(
|
||||
"normal_target",
|
||||
|language, _build_ctx, self_property, function| {
|
||||
|language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let repo = language.repo;
|
||||
let out_property = self_property.and_then(|ref_name| {
|
||||
|
@ -1047,7 +1096,7 @@ fn builtin_ref_name_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Rc
|
|||
);
|
||||
map.insert(
|
||||
"removed_targets",
|
||||
|language, _build_ctx, self_property, function| {
|
||||
|language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let repo = language.repo;
|
||||
let out_property = self_property.and_then(|ref_name| {
|
||||
|
@ -1059,7 +1108,7 @@ fn builtin_ref_name_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Rc
|
|||
);
|
||||
map.insert(
|
||||
"added_targets",
|
||||
|language, _build_ctx, self_property, function| {
|
||||
|language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let repo = language.repo;
|
||||
let out_property = self_property.and_then(|ref_name| {
|
||||
|
@ -1071,7 +1120,7 @@ fn builtin_ref_name_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Rc
|
|||
);
|
||||
map.insert(
|
||||
"tracked",
|
||||
|_language, _build_ctx, self_property, function| {
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|ref_name| ref_name.is_tracked());
|
||||
Ok(L::wrap_boolean(out_property))
|
||||
|
@ -1079,7 +1128,7 @@ fn builtin_ref_name_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Rc
|
|||
);
|
||||
map.insert(
|
||||
"tracking_present",
|
||||
|_language, _build_ctx, self_property, function| {
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|ref_name| ref_name.is_tracking_present());
|
||||
Ok(L::wrap_boolean(out_property))
|
||||
|
@ -1087,7 +1136,7 @@ fn builtin_ref_name_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Rc
|
|||
);
|
||||
map.insert(
|
||||
"tracking_ahead_count",
|
||||
|language, _build_ctx, self_property, function| {
|
||||
|language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let repo = language.repo;
|
||||
let out_property =
|
||||
|
@ -1097,7 +1146,7 @@ fn builtin_ref_name_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Rc
|
|||
);
|
||||
map.insert(
|
||||
"tracking_behind_count",
|
||||
|language, _build_ctx, self_property, function| {
|
||||
|language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let repo = language.repo;
|
||||
let out_property =
|
||||
|
@ -1225,7 +1274,7 @@ fn builtin_commit_or_change_id_methods<'repo>(
|
|||
let mut map = CommitTemplateBuildMethodFnMap::<CommitOrChangeId>::new();
|
||||
map.insert(
|
||||
"normal_hex",
|
||||
|_language, _build_ctx, self_property, function| {
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
Ok(L::wrap_string(self_property.map(|id| {
|
||||
// Note: this is _not_ the same as id.hex() for ChangeId, which
|
||||
|
@ -1238,22 +1287,39 @@ fn builtin_commit_or_change_id_methods<'repo>(
|
|||
})))
|
||||
},
|
||||
);
|
||||
map.insert("short", |language, build_ctx, self_property, function| {
|
||||
map.insert(
|
||||
"short",
|
||||
|language, diagnostics, build_ctx, self_property, function| {
|
||||
let ([], [len_node]) = function.expect_arguments()?;
|
||||
let len_property = len_node
|
||||
.map(|node| template_builder::expect_usize_expression(language, build_ctx, node))
|
||||
.map(|node| {
|
||||
template_builder::expect_usize_expression(
|
||||
language,
|
||||
diagnostics,
|
||||
build_ctx,
|
||||
node,
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
let out_property =
|
||||
(self_property, len_property).map(|(id, len)| id.short(len.unwrap_or(12)));
|
||||
Ok(L::wrap_string(out_property))
|
||||
});
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"shortest",
|
||||
|language, build_ctx, self_property, function| {
|
||||
|language, diagnostics, build_ctx, self_property, function| {
|
||||
let id_prefix_context = &language.id_prefix_context;
|
||||
let ([], [len_node]) = function.expect_arguments()?;
|
||||
let len_property = len_node
|
||||
.map(|node| template_builder::expect_usize_expression(language, build_ctx, node))
|
||||
.map(|node| {
|
||||
template_builder::expect_usize_expression(
|
||||
language,
|
||||
diagnostics,
|
||||
build_ctx,
|
||||
node,
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
let out_property = (self_property, len_property)
|
||||
.map(|(id, len)| id.shortest(language.repo, id_prefix_context, len.unwrap_or(0)));
|
||||
|
@ -1299,27 +1365,36 @@ fn builtin_shortest_id_prefix_methods<'repo>(
|
|||
let mut map = CommitTemplateBuildMethodFnMap::<ShortestIdPrefix>::new();
|
||||
map.insert(
|
||||
"prefix",
|
||||
|_language, _build_ctx, self_property, function| {
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|id| id.prefix);
|
||||
Ok(L::wrap_string(out_property))
|
||||
},
|
||||
);
|
||||
map.insert("rest", |_language, _build_ctx, self_property, function| {
|
||||
map.insert(
|
||||
"rest",
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|id| id.rest);
|
||||
Ok(L::wrap_string(out_property))
|
||||
});
|
||||
map.insert("upper", |_language, _build_ctx, self_property, function| {
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"upper",
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|id| id.to_upper());
|
||||
Ok(L::wrap_shortest_id_prefix(out_property))
|
||||
});
|
||||
map.insert("lower", |_language, _build_ctx, self_property, function| {
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"lower",
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|id| id.to_lower());
|
||||
Ok(L::wrap_shortest_id_prefix(out_property))
|
||||
});
|
||||
},
|
||||
);
|
||||
map
|
||||
}
|
||||
|
||||
|
@ -1392,10 +1467,17 @@ fn builtin_tree_diff_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, T
|
|||
let mut map = CommitTemplateBuildMethodFnMap::<TreeDiff>::new();
|
||||
map.insert(
|
||||
"color_words",
|
||||
|language, build_ctx, self_property, function| {
|
||||
|language, diagnostics, build_ctx, self_property, function| {
|
||||
let ([], [context_node]) = function.expect_arguments()?;
|
||||
let context_property = context_node
|
||||
.map(|node| template_builder::expect_usize_expression(language, build_ctx, node))
|
||||
.map(|node| {
|
||||
template_builder::expect_usize_expression(
|
||||
language,
|
||||
diagnostics,
|
||||
build_ctx,
|
||||
node,
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
let path_converter = language.path_converter;
|
||||
let template = (self_property, context_property)
|
||||
|
@ -1419,10 +1501,19 @@ fn builtin_tree_diff_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, T
|
|||
Ok(L::wrap_template(template))
|
||||
},
|
||||
);
|
||||
map.insert("git", |language, build_ctx, self_property, function| {
|
||||
map.insert(
|
||||
"git",
|
||||
|language, diagnostics, build_ctx, self_property, function| {
|
||||
let ([], [context_node]) = function.expect_arguments()?;
|
||||
let context_property = context_node
|
||||
.map(|node| template_builder::expect_usize_expression(language, build_ctx, node))
|
||||
.map(|node| {
|
||||
template_builder::expect_usize_expression(
|
||||
language,
|
||||
diagnostics,
|
||||
build_ctx,
|
||||
node,
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
let template = (self_property, context_property)
|
||||
.map(|(diff, context)| {
|
||||
|
@ -1433,24 +1524,38 @@ fn builtin_tree_diff_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, T
|
|||
})
|
||||
.into_template();
|
||||
Ok(L::wrap_template(template))
|
||||
});
|
||||
map.insert("stat", |language, build_ctx, self_property, function| {
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"stat",
|
||||
|language, diagnostics, build_ctx, self_property, function| {
|
||||
let [width_node] = function.expect_exact_arguments()?;
|
||||
let width_property =
|
||||
template_builder::expect_usize_expression(language, build_ctx, width_node)?;
|
||||
let width_property = template_builder::expect_usize_expression(
|
||||
language,
|
||||
diagnostics,
|
||||
build_ctx,
|
||||
width_node,
|
||||
)?;
|
||||
let path_converter = language.path_converter;
|
||||
let template = (self_property, width_property)
|
||||
.map(move |(diff, width)| {
|
||||
diff.into_formatted(move |formatter, store, tree_diff| {
|
||||
diff_util::show_diff_stat(formatter, store, tree_diff, path_converter, width)
|
||||
diff_util::show_diff_stat(
|
||||
formatter,
|
||||
store,
|
||||
tree_diff,
|
||||
path_converter,
|
||||
width,
|
||||
)
|
||||
})
|
||||
})
|
||||
.into_template();
|
||||
Ok(L::wrap_template(template))
|
||||
});
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"summary",
|
||||
|language, _build_ctx, self_property, function| {
|
||||
|language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let path_converter = language.path_converter;
|
||||
let template = self_property
|
||||
|
|
|
@ -22,6 +22,7 @@ use crate::template_builder::IntoTemplateProperty;
|
|||
use crate::template_builder::TemplateLanguage;
|
||||
use crate::template_parser;
|
||||
use crate::template_parser::FunctionCallNode;
|
||||
use crate::template_parser::TemplateDiagnostics;
|
||||
use crate::template_parser::TemplateParseResult;
|
||||
use crate::templater::Template;
|
||||
use crate::templater::TemplateProperty;
|
||||
|
@ -86,15 +87,17 @@ impl<'a, C: 'a> TemplateLanguage<'a> for GenericTemplateLanguage<'a, C> {
|
|||
|
||||
fn build_function(
|
||||
&self,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<Self::Property>,
|
||||
function: &FunctionCallNode,
|
||||
) -> TemplateParseResult<Self::Property> {
|
||||
let table = &self.build_fn_table.core;
|
||||
table.build_function(self, build_ctx, function)
|
||||
table.build_function(self, diagnostics, build_ctx, function)
|
||||
}
|
||||
|
||||
fn build_method(
|
||||
&self,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<Self::Property>,
|
||||
property: Self::Property,
|
||||
function: &FunctionCallNode,
|
||||
|
@ -103,7 +106,7 @@ impl<'a, C: 'a> TemplateLanguage<'a> for GenericTemplateLanguage<'a, C> {
|
|||
match property {
|
||||
GenericTemplatePropertyKind::Core(property) => {
|
||||
let table = &self.build_fn_table.core;
|
||||
table.build_method(self, build_ctx, property, function)
|
||||
table.build_method(self, diagnostics, build_ctx, property, function)
|
||||
}
|
||||
GenericTemplatePropertyKind::Self_(property) => {
|
||||
let table = &self.build_fn_table.keywords;
|
||||
|
|
|
@ -32,6 +32,7 @@ use crate::template_builder::TemplateBuildMethodFnMap;
|
|||
use crate::template_builder::TemplateLanguage;
|
||||
use crate::template_parser;
|
||||
use crate::template_parser::FunctionCallNode;
|
||||
use crate::template_parser::TemplateDiagnostics;
|
||||
use crate::template_parser::TemplateParseResult;
|
||||
use crate::templater::PlainTextFormattedProperty;
|
||||
use crate::templater::Template;
|
||||
|
@ -87,15 +88,17 @@ impl TemplateLanguage<'static> for OperationTemplateLanguage {
|
|||
|
||||
fn build_function(
|
||||
&self,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<Self::Property>,
|
||||
function: &FunctionCallNode,
|
||||
) -> TemplateParseResult<Self::Property> {
|
||||
let table = &self.build_fn_table.core;
|
||||
table.build_function(self, build_ctx, function)
|
||||
table.build_function(self, diagnostics, build_ctx, function)
|
||||
}
|
||||
|
||||
fn build_method(
|
||||
&self,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<Self::Property>,
|
||||
property: Self::Property,
|
||||
function: &FunctionCallNode,
|
||||
|
@ -104,17 +107,17 @@ impl TemplateLanguage<'static> for OperationTemplateLanguage {
|
|||
match property {
|
||||
OperationTemplatePropertyKind::Core(property) => {
|
||||
let table = &self.build_fn_table.core;
|
||||
table.build_method(self, build_ctx, property, function)
|
||||
table.build_method(self, diagnostics, build_ctx, property, function)
|
||||
}
|
||||
OperationTemplatePropertyKind::Operation(property) => {
|
||||
let table = &self.build_fn_table.operation_methods;
|
||||
let build = template_parser::lookup_method(type_name, table, function)?;
|
||||
build(self, build_ctx, property, function)
|
||||
build(self, diagnostics, build_ctx, property, function)
|
||||
}
|
||||
OperationTemplatePropertyKind::OperationId(property) => {
|
||||
let table = &self.build_fn_table.operation_id_methods;
|
||||
let build = template_parser::lookup_method(type_name, table, function)?;
|
||||
build(self, build_ctx, property, function)
|
||||
build(self, diagnostics, build_ctx, property, function)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -236,7 +239,7 @@ fn builtin_operation_methods() -> OperationTemplateBuildMethodFnMap<Operation> {
|
|||
let mut map = OperationTemplateBuildMethodFnMap::<Operation>::new();
|
||||
map.insert(
|
||||
"current_operation",
|
||||
|language, _build_ctx, self_property, function| {
|
||||
|language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let current_op_id = language.current_op_id.clone();
|
||||
let out_property = self_property.map(move |op| Some(op.id()) == current_op_id.as_ref());
|
||||
|
@ -245,18 +248,23 @@ fn builtin_operation_methods() -> OperationTemplateBuildMethodFnMap<Operation> {
|
|||
);
|
||||
map.insert(
|
||||
"description",
|
||||
|_language, _build_ctx, self_property, function| {
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|op| op.metadata().description.clone());
|
||||
Ok(L::wrap_string(out_property))
|
||||
},
|
||||
);
|
||||
map.insert("id", |_language, _build_ctx, self_property, function| {
|
||||
map.insert(
|
||||
"id",
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|op| op.id().clone());
|
||||
Ok(L::wrap_operation_id(out_property))
|
||||
});
|
||||
map.insert("tags", |_language, _build_ctx, self_property, function| {
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"tags",
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|op| {
|
||||
// TODO: introduce map type
|
||||
|
@ -267,37 +275,47 @@ fn builtin_operation_methods() -> OperationTemplateBuildMethodFnMap<Operation> {
|
|||
.join("\n")
|
||||
});
|
||||
Ok(L::wrap_string(out_property))
|
||||
});
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"snapshot",
|
||||
|_language, _build_ctx, self_property, function| {
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|op| op.metadata().is_snapshot);
|
||||
Ok(L::wrap_boolean(out_property))
|
||||
},
|
||||
);
|
||||
map.insert("time", |_language, _build_ctx, self_property, function| {
|
||||
map.insert(
|
||||
"time",
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|op| TimestampRange {
|
||||
start: op.metadata().start_time,
|
||||
end: op.metadata().end_time,
|
||||
});
|
||||
Ok(L::wrap_timestamp_range(out_property))
|
||||
});
|
||||
map.insert("user", |_language, _build_ctx, self_property, function| {
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"user",
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|op| {
|
||||
// TODO: introduce dedicated type and provide accessors?
|
||||
format!("{}@{}", op.metadata().username, op.metadata().hostname)
|
||||
});
|
||||
Ok(L::wrap_string(out_property))
|
||||
});
|
||||
map.insert("root", |language, _build_ctx, self_property, function| {
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"root",
|
||||
|language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let root_op_id = language.root_op_id.clone();
|
||||
let out_property = self_property.map(move |op| op.id() == &root_op_id);
|
||||
Ok(L::wrap_boolean(out_property))
|
||||
});
|
||||
},
|
||||
);
|
||||
map
|
||||
}
|
||||
|
||||
|
@ -312,10 +330,19 @@ fn builtin_operation_id_methods() -> OperationTemplateBuildMethodFnMap<Operation
|
|||
// Not using maplit::hashmap!{} or custom declarative macro here because
|
||||
// code completion inside macro is quite restricted.
|
||||
let mut map = OperationTemplateBuildMethodFnMap::<OperationId>::new();
|
||||
map.insert("short", |language, build_ctx, self_property, function| {
|
||||
map.insert(
|
||||
"short",
|
||||
|language, diagnostics, build_ctx, self_property, function| {
|
||||
let ([], [len_node]) = function.expect_arguments()?;
|
||||
let len_property = len_node
|
||||
.map(|node| template_builder::expect_usize_expression(language, build_ctx, node))
|
||||
.map(|node| {
|
||||
template_builder::expect_usize_expression(
|
||||
language,
|
||||
diagnostics,
|
||||
build_ctx,
|
||||
node,
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
let out_property = (self_property, len_property).map(|(id, len)| {
|
||||
let mut hex = id.hex();
|
||||
|
@ -323,6 +350,7 @@ fn builtin_operation_id_methods() -> OperationTemplateBuildMethodFnMap<Operation
|
|||
hex
|
||||
});
|
||||
Ok(L::wrap_string(out_property))
|
||||
});
|
||||
},
|
||||
);
|
||||
map
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ use crate::template_parser::ExpressionKind;
|
|||
use crate::template_parser::ExpressionNode;
|
||||
use crate::template_parser::FunctionCallNode;
|
||||
use crate::template_parser::TemplateAliasesMap;
|
||||
use crate::template_parser::TemplateDiagnostics;
|
||||
use crate::template_parser::TemplateParseError;
|
||||
use crate::template_parser::TemplateParseErrorKind;
|
||||
use crate::template_parser::TemplateParseResult;
|
||||
|
@ -79,12 +80,14 @@ pub trait TemplateLanguage<'a> {
|
|||
/// `CoreTemplateBuildFnTable::build_function()`.
|
||||
fn build_function(
|
||||
&self,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<Self::Property>,
|
||||
function: &FunctionCallNode,
|
||||
) -> TemplateParseResult<Self::Property>;
|
||||
|
||||
fn build_method(
|
||||
&self,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<Self::Property>,
|
||||
property: Self::Property,
|
||||
function: &FunctionCallNode,
|
||||
|
@ -269,6 +272,7 @@ impl<'a> IntoTemplateProperty<'a> for CoreTemplatePropertyKind<'a> {
|
|||
pub type TemplateBuildFunctionFn<'a, L> =
|
||||
fn(
|
||||
&L,
|
||||
&mut TemplateDiagnostics,
|
||||
&BuildContext<<L as TemplateLanguage<'a>>::Property>,
|
||||
&FunctionCallNode,
|
||||
) -> TemplateParseResult<<L as TemplateLanguage<'a>>::Property>;
|
||||
|
@ -277,6 +281,7 @@ pub type TemplateBuildFunctionFn<'a, L> =
|
|||
pub type TemplateBuildMethodFn<'a, L, T> =
|
||||
fn(
|
||||
&L,
|
||||
&mut TemplateDiagnostics,
|
||||
&BuildContext<<L as TemplateLanguage<'a>>::Property>,
|
||||
Box<dyn TemplateProperty<Output = T> + 'a>,
|
||||
&FunctionCallNode,
|
||||
|
@ -363,12 +368,13 @@ impl<'a, L: TemplateLanguage<'a> + ?Sized> CoreTemplateBuildFnTable<'a, L> {
|
|||
pub fn build_function(
|
||||
&self,
|
||||
language: &L,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<L::Property>,
|
||||
function: &FunctionCallNode,
|
||||
) -> TemplateParseResult<L::Property> {
|
||||
let table = &self.functions;
|
||||
let build = template_parser::lookup_function(table, function)?;
|
||||
build(language, build_ctx, function)
|
||||
build(language, diagnostics, build_ctx, function)
|
||||
}
|
||||
|
||||
/// Applies the method call node `function` to the given `property` by using
|
||||
|
@ -376,6 +382,7 @@ impl<'a, L: TemplateLanguage<'a> + ?Sized> CoreTemplateBuildFnTable<'a, L> {
|
|||
pub fn build_method(
|
||||
&self,
|
||||
language: &L,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<L::Property>,
|
||||
property: CoreTemplatePropertyKind<'a>,
|
||||
function: &FunctionCallNode,
|
||||
|
@ -385,50 +392,61 @@ impl<'a, L: TemplateLanguage<'a> + ?Sized> CoreTemplateBuildFnTable<'a, L> {
|
|||
CoreTemplatePropertyKind::String(property) => {
|
||||
let table = &self.string_methods;
|
||||
let build = template_parser::lookup_method(type_name, table, function)?;
|
||||
build(language, build_ctx, property, function)
|
||||
build(language, diagnostics, build_ctx, property, function)
|
||||
}
|
||||
CoreTemplatePropertyKind::StringList(property) => {
|
||||
// TODO: migrate to table?
|
||||
build_formattable_list_method(language, build_ctx, property, function, |item| {
|
||||
L::wrap_string(item)
|
||||
})
|
||||
build_formattable_list_method(
|
||||
language,
|
||||
diagnostics,
|
||||
build_ctx,
|
||||
property,
|
||||
function,
|
||||
L::wrap_string,
|
||||
)
|
||||
}
|
||||
CoreTemplatePropertyKind::Boolean(property) => {
|
||||
let table = &self.boolean_methods;
|
||||
let build = template_parser::lookup_method(type_name, table, function)?;
|
||||
build(language, build_ctx, property, function)
|
||||
build(language, diagnostics, build_ctx, property, function)
|
||||
}
|
||||
CoreTemplatePropertyKind::Integer(property) => {
|
||||
let table = &self.integer_methods;
|
||||
let build = template_parser::lookup_method(type_name, table, function)?;
|
||||
build(language, build_ctx, property, function)
|
||||
build(language, diagnostics, build_ctx, property, function)
|
||||
}
|
||||
CoreTemplatePropertyKind::IntegerOpt(property) => {
|
||||
let type_name = "Integer";
|
||||
let table = &self.integer_methods;
|
||||
let build = template_parser::lookup_method(type_name, table, function)?;
|
||||
let inner_property = property.try_unwrap(type_name);
|
||||
build(language, build_ctx, Box::new(inner_property), function)
|
||||
build(
|
||||
language,
|
||||
diagnostics,
|
||||
build_ctx,
|
||||
Box::new(inner_property),
|
||||
function,
|
||||
)
|
||||
}
|
||||
CoreTemplatePropertyKind::Signature(property) => {
|
||||
let table = &self.signature_methods;
|
||||
let build = template_parser::lookup_method(type_name, table, function)?;
|
||||
build(language, build_ctx, property, function)
|
||||
build(language, diagnostics, build_ctx, property, function)
|
||||
}
|
||||
CoreTemplatePropertyKind::SizeHint(property) => {
|
||||
let table = &self.size_hint_methods;
|
||||
let build = template_parser::lookup_method(type_name, table, function)?;
|
||||
build(language, build_ctx, property, function)
|
||||
build(language, diagnostics, build_ctx, property, function)
|
||||
}
|
||||
CoreTemplatePropertyKind::Timestamp(property) => {
|
||||
let table = &self.timestamp_methods;
|
||||
let build = template_parser::lookup_method(type_name, table, function)?;
|
||||
build(language, build_ctx, property, function)
|
||||
build(language, diagnostics, build_ctx, property, function)
|
||||
}
|
||||
CoreTemplatePropertyKind::TimestampRange(property) => {
|
||||
let table = &self.timestamp_range_methods;
|
||||
let build = template_parser::lookup_method(type_name, table, function)?;
|
||||
build(language, build_ctx, property, function)
|
||||
build(language, diagnostics, build_ctx, property, function)
|
||||
}
|
||||
CoreTemplatePropertyKind::Template(_) => {
|
||||
// TODO: migrate to table?
|
||||
|
@ -436,7 +454,7 @@ impl<'a, L: TemplateLanguage<'a> + ?Sized> CoreTemplateBuildFnTable<'a, L> {
|
|||
}
|
||||
CoreTemplatePropertyKind::ListTemplate(template) => {
|
||||
// TODO: migrate to table?
|
||||
build_list_template_method(language, build_ctx, template, function)
|
||||
build_list_template_method(language, diagnostics, build_ctx, template, function)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -499,6 +517,7 @@ pub struct BuildContext<'i, P> {
|
|||
|
||||
fn build_keyword<'a, L: TemplateLanguage<'a> + ?Sized>(
|
||||
language: &L,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<L::Property>,
|
||||
name: &str,
|
||||
name_span: pest::Span<'_>,
|
||||
|
@ -513,7 +532,7 @@ fn build_keyword<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
args_span: name_span.end_pos().span(&name_span.end_pos()),
|
||||
};
|
||||
language
|
||||
.build_method(build_ctx, self_property, &function)
|
||||
.build_method(diagnostics, build_ctx, self_property, &function)
|
||||
.map_err(|err| match err.kind() {
|
||||
TemplateParseErrorKind::NoSuchMethod { candidates, .. } => {
|
||||
let kind = TemplateParseErrorKind::NoSuchKeyword {
|
||||
|
@ -540,17 +559,18 @@ fn build_keyword<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
|
||||
fn build_unary_operation<'a, L: TemplateLanguage<'a> + ?Sized>(
|
||||
language: &L,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<L::Property>,
|
||||
op: UnaryOp,
|
||||
arg_node: &ExpressionNode,
|
||||
) -> TemplateParseResult<L::Property> {
|
||||
match op {
|
||||
UnaryOp::LogicalNot => {
|
||||
let arg = expect_boolean_expression(language, build_ctx, arg_node)?;
|
||||
let arg = expect_boolean_expression(language, diagnostics, build_ctx, arg_node)?;
|
||||
Ok(L::wrap_boolean(arg.map(|v| !v)))
|
||||
}
|
||||
UnaryOp::Negate => {
|
||||
let arg = expect_integer_expression(language, build_ctx, arg_node)?;
|
||||
let arg = expect_integer_expression(language, diagnostics, build_ctx, arg_node)?;
|
||||
Ok(L::wrap_integer(arg.and_then(|v| {
|
||||
v.checked_neg()
|
||||
.ok_or_else(|| TemplatePropertyError("Attempt to negate with overflow".into()))
|
||||
|
@ -561,6 +581,7 @@ fn build_unary_operation<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
|
||||
fn build_binary_operation<'a, L: TemplateLanguage<'a> + ?Sized>(
|
||||
language: &L,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<L::Property>,
|
||||
op: BinaryOp,
|
||||
lhs_node: &ExpressionNode,
|
||||
|
@ -568,14 +589,14 @@ fn build_binary_operation<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
) -> TemplateParseResult<L::Property> {
|
||||
match op {
|
||||
BinaryOp::LogicalOr => {
|
||||
let lhs = expect_boolean_expression(language, build_ctx, lhs_node)?;
|
||||
let rhs = expect_boolean_expression(language, build_ctx, rhs_node)?;
|
||||
let lhs = expect_boolean_expression(language, diagnostics, build_ctx, lhs_node)?;
|
||||
let rhs = expect_boolean_expression(language, diagnostics, build_ctx, rhs_node)?;
|
||||
let out = lhs.and_then(move |l| Ok(l || rhs.extract()?));
|
||||
Ok(L::wrap_boolean(out))
|
||||
}
|
||||
BinaryOp::LogicalAnd => {
|
||||
let lhs = expect_boolean_expression(language, build_ctx, lhs_node)?;
|
||||
let rhs = expect_boolean_expression(language, build_ctx, rhs_node)?;
|
||||
let lhs = expect_boolean_expression(language, diagnostics, build_ctx, lhs_node)?;
|
||||
let rhs = expect_boolean_expression(language, diagnostics, build_ctx, rhs_node)?;
|
||||
let out = lhs.and_then(move |l| Ok(l && rhs.extract()?));
|
||||
Ok(L::wrap_boolean(out))
|
||||
}
|
||||
|
@ -587,17 +608,21 @@ fn builtin_string_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
// Not using maplit::hashmap!{} or custom declarative macro here because
|
||||
// code completion inside macro is quite restricted.
|
||||
let mut map = TemplateBuildMethodFnMap::<L, String>::new();
|
||||
map.insert("len", |_language, _build_ctx, self_property, function| {
|
||||
map.insert(
|
||||
"len",
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.and_then(|s| Ok(s.len().try_into()?));
|
||||
Ok(L::wrap_integer(out_property))
|
||||
});
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"contains",
|
||||
|language, build_ctx, self_property, function| {
|
||||
|language, diagnostics, build_ctx, self_property, function| {
|
||||
let [needle_node] = function.expect_exact_arguments()?;
|
||||
// TODO: or .try_into_string() to disable implicit type cast?
|
||||
let needle_property = expect_plain_text_expression(language, build_ctx, needle_node)?;
|
||||
let needle_property =
|
||||
expect_plain_text_expression(language, diagnostics, build_ctx, needle_node)?;
|
||||
let out_property = (self_property, needle_property)
|
||||
.map(|(haystack, needle)| haystack.contains(&needle));
|
||||
Ok(L::wrap_boolean(out_property))
|
||||
|
@ -605,9 +630,10 @@ fn builtin_string_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
);
|
||||
map.insert(
|
||||
"starts_with",
|
||||
|language, build_ctx, self_property, function| {
|
||||
|language, diagnostics, build_ctx, self_property, function| {
|
||||
let [needle_node] = function.expect_exact_arguments()?;
|
||||
let needle_property = expect_plain_text_expression(language, build_ctx, needle_node)?;
|
||||
let needle_property =
|
||||
expect_plain_text_expression(language, diagnostics, build_ctx, needle_node)?;
|
||||
let out_property = (self_property, needle_property)
|
||||
.map(|(haystack, needle)| haystack.starts_with(&needle));
|
||||
Ok(L::wrap_boolean(out_property))
|
||||
|
@ -615,9 +641,10 @@ fn builtin_string_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
);
|
||||
map.insert(
|
||||
"ends_with",
|
||||
|language, build_ctx, self_property, function| {
|
||||
|language, diagnostics, build_ctx, self_property, function| {
|
||||
let [needle_node] = function.expect_exact_arguments()?;
|
||||
let needle_property = expect_plain_text_expression(language, build_ctx, needle_node)?;
|
||||
let needle_property =
|
||||
expect_plain_text_expression(language, diagnostics, build_ctx, needle_node)?;
|
||||
let out_property = (self_property, needle_property)
|
||||
.map(|(haystack, needle)| haystack.ends_with(&needle));
|
||||
Ok(L::wrap_boolean(out_property))
|
||||
|
@ -625,9 +652,10 @@ fn builtin_string_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
);
|
||||
map.insert(
|
||||
"remove_prefix",
|
||||
|language, build_ctx, self_property, function| {
|
||||
|language, diagnostics, build_ctx, self_property, function| {
|
||||
let [needle_node] = function.expect_exact_arguments()?;
|
||||
let needle_property = expect_plain_text_expression(language, build_ctx, needle_node)?;
|
||||
let needle_property =
|
||||
expect_plain_text_expression(language, diagnostics, build_ctx, needle_node)?;
|
||||
let out_property = (self_property, needle_property).map(|(haystack, needle)| {
|
||||
haystack
|
||||
.strip_prefix(&needle)
|
||||
|
@ -639,9 +667,10 @@ fn builtin_string_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
);
|
||||
map.insert(
|
||||
"remove_suffix",
|
||||
|language, build_ctx, self_property, function| {
|
||||
|language, diagnostics, build_ctx, self_property, function| {
|
||||
let [needle_node] = function.expect_exact_arguments()?;
|
||||
let needle_property = expect_plain_text_expression(language, build_ctx, needle_node)?;
|
||||
let needle_property =
|
||||
expect_plain_text_expression(language, diagnostics, build_ctx, needle_node)?;
|
||||
let out_property = (self_property, needle_property).map(|(haystack, needle)| {
|
||||
haystack
|
||||
.strip_suffix(&needle)
|
||||
|
@ -651,42 +680,57 @@ fn builtin_string_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
Ok(L::wrap_string(out_property))
|
||||
},
|
||||
);
|
||||
map.insert("substr", |language, build_ctx, self_property, function| {
|
||||
map.insert(
|
||||
"substr",
|
||||
|language, diagnostics, build_ctx, self_property, function| {
|
||||
let [start_idx, end_idx] = function.expect_exact_arguments()?;
|
||||
let start_idx_property = expect_isize_expression(language, build_ctx, start_idx)?;
|
||||
let end_idx_property = expect_isize_expression(language, build_ctx, end_idx)?;
|
||||
let out_property =
|
||||
(self_property, start_idx_property, end_idx_property).map(|(s, start_idx, end_idx)| {
|
||||
let start_idx_property =
|
||||
expect_isize_expression(language, diagnostics, build_ctx, start_idx)?;
|
||||
let end_idx_property =
|
||||
expect_isize_expression(language, diagnostics, build_ctx, end_idx)?;
|
||||
let out_property = (self_property, start_idx_property, end_idx_property).map(
|
||||
|(s, start_idx, end_idx)| {
|
||||
let start_idx = string_index_to_char_boundary(&s, start_idx);
|
||||
let end_idx = string_index_to_char_boundary(&s, end_idx);
|
||||
s.get(start_idx..end_idx).unwrap_or_default().to_owned()
|
||||
});
|
||||
},
|
||||
);
|
||||
Ok(L::wrap_string(out_property))
|
||||
});
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"first_line",
|
||||
|_language, _build_ctx, self_property, function| {
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property =
|
||||
self_property.map(|s| s.lines().next().unwrap_or_default().to_string());
|
||||
Ok(L::wrap_string(out_property))
|
||||
},
|
||||
);
|
||||
map.insert("lines", |_language, _build_ctx, self_property, function| {
|
||||
map.insert(
|
||||
"lines",
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|s| s.lines().map(|l| l.to_owned()).collect());
|
||||
Ok(L::wrap_string_list(out_property))
|
||||
});
|
||||
map.insert("upper", |_language, _build_ctx, self_property, function| {
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"upper",
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|s| s.to_uppercase());
|
||||
Ok(L::wrap_string(out_property))
|
||||
});
|
||||
map.insert("lower", |_language, _build_ctx, self_property, function| {
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"lower",
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|s| s.to_lowercase());
|
||||
Ok(L::wrap_string(out_property))
|
||||
});
|
||||
},
|
||||
);
|
||||
map
|
||||
}
|
||||
|
||||
|
@ -711,19 +755,25 @@ fn builtin_signature_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
// Not using maplit::hashmap!{} or custom declarative macro here because
|
||||
// code completion inside macro is quite restricted.
|
||||
let mut map = TemplateBuildMethodFnMap::<L, Signature>::new();
|
||||
map.insert("name", |_language, _build_ctx, self_property, function| {
|
||||
map.insert(
|
||||
"name",
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|signature| signature.name);
|
||||
Ok(L::wrap_string(out_property))
|
||||
});
|
||||
map.insert("email", |_language, _build_ctx, self_property, function| {
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"email",
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|signature| signature.email);
|
||||
Ok(L::wrap_string(out_property))
|
||||
});
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"username",
|
||||
|_language, _build_ctx, self_property, function| {
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|signature| {
|
||||
let (username, _) = text_util::split_email(&signature.email);
|
||||
|
@ -734,7 +784,7 @@ fn builtin_signature_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
);
|
||||
map.insert(
|
||||
"timestamp",
|
||||
|_language, _build_ctx, self_property, function| {
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|signature| signature.timestamp);
|
||||
Ok(L::wrap_timestamp(out_property))
|
||||
|
@ -748,30 +798,42 @@ fn builtin_size_hint_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
// Not using maplit::hashmap!{} or custom declarative macro here because
|
||||
// code completion inside macro is quite restricted.
|
||||
let mut map = TemplateBuildMethodFnMap::<L, SizeHint>::new();
|
||||
map.insert("lower", |_language, _build_ctx, self_property, function| {
|
||||
map.insert(
|
||||
"lower",
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.and_then(|(lower, _)| Ok(i64::try_from(lower)?));
|
||||
Ok(L::wrap_integer(out_property))
|
||||
});
|
||||
map.insert("upper", |_language, _build_ctx, self_property, function| {
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"upper",
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property =
|
||||
self_property.and_then(|(_, upper)| Ok(upper.map(i64::try_from).transpose()?));
|
||||
Ok(L::wrap_integer_opt(out_property))
|
||||
});
|
||||
map.insert("exact", |_language, _build_ctx, self_property, function| {
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"exact",
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.and_then(|(lower, upper)| {
|
||||
let exact = (Some(lower) == upper).then_some(lower);
|
||||
Ok(exact.map(i64::try_from).transpose()?)
|
||||
});
|
||||
Ok(L::wrap_integer_opt(out_property))
|
||||
});
|
||||
map.insert("zero", |_language, _build_ctx, self_property, function| {
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"zero",
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|(_, upper)| upper == Some(0));
|
||||
Ok(L::wrap_boolean(out_property))
|
||||
});
|
||||
},
|
||||
);
|
||||
map
|
||||
}
|
||||
|
||||
|
@ -780,17 +842,21 @@ fn builtin_timestamp_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
// Not using maplit::hashmap!{} or custom declarative macro here because
|
||||
// code completion inside macro is quite restricted.
|
||||
let mut map = TemplateBuildMethodFnMap::<L, Timestamp>::new();
|
||||
map.insert("ago", |_language, _build_ctx, self_property, function| {
|
||||
map.insert(
|
||||
"ago",
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let now = Timestamp::now();
|
||||
let format = timeago::Formatter::new();
|
||||
let out_property = self_property
|
||||
.and_then(move |timestamp| Ok(time_util::format_duration(×tamp, &now, &format)?));
|
||||
Ok(L::wrap_string(out_property))
|
||||
let out_property = self_property.and_then(move |timestamp| {
|
||||
Ok(time_util::format_duration(×tamp, &now, &format)?)
|
||||
});
|
||||
Ok(L::wrap_string(out_property))
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"format",
|
||||
|_language, _build_ctx, self_property, function| {
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
// No dynamic string is allowed as the templater has no runtime error type.
|
||||
let [format_node] = function.expect_exact_arguments()?;
|
||||
let format =
|
||||
|
@ -807,15 +873,20 @@ fn builtin_timestamp_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
Ok(L::wrap_string(out_property))
|
||||
},
|
||||
);
|
||||
map.insert("utc", |_language, _build_ctx, self_property, function| {
|
||||
map.insert(
|
||||
"utc",
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|mut timestamp| {
|
||||
timestamp.tz_offset = 0;
|
||||
timestamp
|
||||
});
|
||||
Ok(L::wrap_timestamp(out_property))
|
||||
});
|
||||
map.insert("local", |_language, _build_ctx, self_property, function| {
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"local",
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let tz_offset = std::env::var("JJ_TZ_OFFSET_MINS")
|
||||
.ok()
|
||||
|
@ -826,7 +897,8 @@ fn builtin_timestamp_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
timestamp
|
||||
});
|
||||
Ok(L::wrap_timestamp(out_property))
|
||||
});
|
||||
},
|
||||
);
|
||||
map
|
||||
}
|
||||
|
||||
|
@ -835,19 +907,25 @@ fn builtin_timestamp_range_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
// Not using maplit::hashmap!{} or custom declarative macro here because
|
||||
// code completion inside macro is quite restricted.
|
||||
let mut map = TemplateBuildMethodFnMap::<L, TimestampRange>::new();
|
||||
map.insert("start", |_language, _build_ctx, self_property, function| {
|
||||
map.insert(
|
||||
"start",
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|time_range| time_range.start);
|
||||
Ok(L::wrap_timestamp(out_property))
|
||||
});
|
||||
map.insert("end", |_language, _build_ctx, self_property, function| {
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"end",
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.map(|time_range| time_range.end);
|
||||
Ok(L::wrap_timestamp(out_property))
|
||||
});
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"duration",
|
||||
|_language, _build_ctx, self_property, function| {
|
||||
|_language, _diagnostics, _build_ctx, self_property, function| {
|
||||
function.expect_no_arguments()?;
|
||||
let out_property = self_property.and_then(|time_range| Ok(time_range.duration()?));
|
||||
Ok(L::wrap_string(out_property))
|
||||
|
@ -858,6 +936,7 @@ fn builtin_timestamp_range_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
|
||||
fn build_list_template_method<'a, L: TemplateLanguage<'a> + ?Sized>(
|
||||
language: &L,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<L::Property>,
|
||||
self_template: Box<dyn ListTemplate + 'a>,
|
||||
function: &FunctionCallNode,
|
||||
|
@ -865,7 +944,8 @@ fn build_list_template_method<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
let property = match function.name {
|
||||
"join" => {
|
||||
let [separator_node] = function.expect_exact_arguments()?;
|
||||
let separator = expect_template_expression(language, build_ctx, separator_node)?;
|
||||
let separator =
|
||||
expect_template_expression(language, diagnostics, build_ctx, separator_node)?;
|
||||
L::wrap_template(self_template.join(separator))
|
||||
}
|
||||
_ => return Err(TemplateParseError::no_such_method("ListTemplate", function)),
|
||||
|
@ -876,6 +956,7 @@ fn build_list_template_method<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
/// Builds method call expression for printable list property.
|
||||
pub fn build_formattable_list_method<'a, L, O>(
|
||||
language: &L,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<L::Property>,
|
||||
self_property: impl TemplateProperty<Output = Vec<O>> + 'a,
|
||||
function: &FunctionCallNode,
|
||||
|
@ -895,14 +976,22 @@ where
|
|||
}
|
||||
"join" => {
|
||||
let [separator_node] = function.expect_exact_arguments()?;
|
||||
let separator = expect_template_expression(language, build_ctx, separator_node)?;
|
||||
let separator =
|
||||
expect_template_expression(language, diagnostics, build_ctx, separator_node)?;
|
||||
let template =
|
||||
ListPropertyTemplate::new(self_property, separator, |formatter, item| {
|
||||
item.format(formatter)
|
||||
});
|
||||
L::wrap_template(Box::new(template))
|
||||
}
|
||||
"map" => build_map_operation(language, build_ctx, self_property, function, wrap_item)?,
|
||||
"map" => build_map_operation(
|
||||
language,
|
||||
diagnostics,
|
||||
build_ctx,
|
||||
self_property,
|
||||
function,
|
||||
wrap_item,
|
||||
)?,
|
||||
_ => return Err(TemplateParseError::no_such_method("List", function)),
|
||||
};
|
||||
Ok(property)
|
||||
|
@ -910,6 +999,7 @@ where
|
|||
|
||||
pub fn build_unformattable_list_method<'a, L, O>(
|
||||
language: &L,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<L::Property>,
|
||||
self_property: impl TemplateProperty<Output = Vec<O>> + 'a,
|
||||
function: &FunctionCallNode,
|
||||
|
@ -926,7 +1016,14 @@ where
|
|||
L::wrap_integer(out_property)
|
||||
}
|
||||
// No "join"
|
||||
"map" => build_map_operation(language, build_ctx, self_property, function, wrap_item)?,
|
||||
"map" => build_map_operation(
|
||||
language,
|
||||
diagnostics,
|
||||
build_ctx,
|
||||
self_property,
|
||||
function,
|
||||
wrap_item,
|
||||
)?,
|
||||
_ => return Err(TemplateParseError::no_such_method("List", function)),
|
||||
};
|
||||
Ok(property)
|
||||
|
@ -938,6 +1035,7 @@ where
|
|||
/// `wrap_item()` is the function to wrap a list item of type `O` as a property.
|
||||
fn build_map_operation<'a, L, O, P>(
|
||||
language: &L,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<L::Property>,
|
||||
self_property: P,
|
||||
function: &FunctionCallNode,
|
||||
|
@ -968,7 +1066,7 @@ where
|
|||
local_variables,
|
||||
self_variable: build_ctx.self_variable,
|
||||
};
|
||||
expect_template_expression(language, &inner_build_ctx, &lambda.body)
|
||||
expect_template_expression(language, diagnostics, &inner_build_ctx, &lambda.body)
|
||||
})?;
|
||||
let list_template = ListPropertyTemplate::new(
|
||||
self_property,
|
||||
|
@ -984,10 +1082,10 @@ fn builtin_functions<'a, L: TemplateLanguage<'a> + ?Sized>() -> TemplateBuildFun
|
|||
// Not using maplit::hashmap!{} or custom declarative macro here because
|
||||
// code completion inside macro is quite restricted.
|
||||
let mut map = TemplateBuildFunctionFnMap::<L>::new();
|
||||
map.insert("fill", |language, build_ctx, function| {
|
||||
map.insert("fill", |language, diagnostics, build_ctx, function| {
|
||||
let [width_node, content_node] = function.expect_exact_arguments()?;
|
||||
let width = expect_usize_expression(language, build_ctx, width_node)?;
|
||||
let content = expect_template_expression(language, build_ctx, content_node)?;
|
||||
let width = expect_usize_expression(language, diagnostics, build_ctx, width_node)?;
|
||||
let content = expect_template_expression(language, diagnostics, build_ctx, content_node)?;
|
||||
let template =
|
||||
ReformatTemplate::new(content, move |formatter, recorded| match width.extract() {
|
||||
Ok(width) => text_util::write_wrapped(formatter.as_mut(), recorded, width),
|
||||
|
@ -995,10 +1093,10 @@ fn builtin_functions<'a, L: TemplateLanguage<'a> + ?Sized>() -> TemplateBuildFun
|
|||
});
|
||||
Ok(L::wrap_template(Box::new(template)))
|
||||
});
|
||||
map.insert("indent", |language, build_ctx, function| {
|
||||
map.insert("indent", |language, diagnostics, build_ctx, function| {
|
||||
let [prefix_node, content_node] = function.expect_exact_arguments()?;
|
||||
let prefix = expect_template_expression(language, build_ctx, prefix_node)?;
|
||||
let content = expect_template_expression(language, build_ctx, content_node)?;
|
||||
let prefix = expect_template_expression(language, diagnostics, build_ctx, prefix_node)?;
|
||||
let content = expect_template_expression(language, diagnostics, build_ctx, content_node)?;
|
||||
let template = ReformatTemplate::new(content, move |formatter, recorded| {
|
||||
let rewrap = formatter.rewrap_fn();
|
||||
text_util::write_indented(formatter.as_mut(), recorded, |formatter| {
|
||||
|
@ -1007,58 +1105,62 @@ fn builtin_functions<'a, L: TemplateLanguage<'a> + ?Sized>() -> TemplateBuildFun
|
|||
});
|
||||
Ok(L::wrap_template(Box::new(template)))
|
||||
});
|
||||
map.insert("label", |language, build_ctx, function| {
|
||||
map.insert("label", |language, diagnostics, build_ctx, function| {
|
||||
let [label_node, content_node] = function.expect_exact_arguments()?;
|
||||
let label_property = expect_plain_text_expression(language, build_ctx, label_node)?;
|
||||
let content = expect_template_expression(language, build_ctx, content_node)?;
|
||||
let label_property =
|
||||
expect_plain_text_expression(language, diagnostics, build_ctx, label_node)?;
|
||||
let content = expect_template_expression(language, diagnostics, build_ctx, content_node)?;
|
||||
let labels =
|
||||
label_property.map(|s| s.split_whitespace().map(ToString::to_string).collect());
|
||||
Ok(L::wrap_template(Box::new(LabelTemplate::new(
|
||||
content, labels,
|
||||
))))
|
||||
});
|
||||
map.insert("if", |language, build_ctx, function| {
|
||||
map.insert("if", |language, diagnostics, build_ctx, function| {
|
||||
let ([condition_node, true_node], [false_node]) = function.expect_arguments()?;
|
||||
let condition = expect_boolean_expression(language, build_ctx, condition_node)?;
|
||||
let true_template = expect_template_expression(language, build_ctx, true_node)?;
|
||||
let condition =
|
||||
expect_boolean_expression(language, diagnostics, build_ctx, condition_node)?;
|
||||
let true_template =
|
||||
expect_template_expression(language, diagnostics, build_ctx, true_node)?;
|
||||
let false_template = false_node
|
||||
.map(|node| expect_template_expression(language, build_ctx, node))
|
||||
.map(|node| expect_template_expression(language, diagnostics, build_ctx, node))
|
||||
.transpose()?;
|
||||
let template = ConditionalTemplate::new(condition, true_template, false_template);
|
||||
Ok(L::wrap_template(Box::new(template)))
|
||||
});
|
||||
map.insert("coalesce", |language, build_ctx, function| {
|
||||
map.insert("coalesce", |language, diagnostics, build_ctx, function| {
|
||||
let contents = function
|
||||
.args
|
||||
.iter()
|
||||
.map(|node| expect_template_expression(language, build_ctx, node))
|
||||
.map(|node| expect_template_expression(language, diagnostics, build_ctx, node))
|
||||
.try_collect()?;
|
||||
Ok(L::wrap_template(Box::new(CoalesceTemplate(contents))))
|
||||
});
|
||||
map.insert("concat", |language, build_ctx, function| {
|
||||
map.insert("concat", |language, diagnostics, build_ctx, function| {
|
||||
let contents = function
|
||||
.args
|
||||
.iter()
|
||||
.map(|node| expect_template_expression(language, build_ctx, node))
|
||||
.map(|node| expect_template_expression(language, diagnostics, build_ctx, node))
|
||||
.try_collect()?;
|
||||
Ok(L::wrap_template(Box::new(ConcatTemplate(contents))))
|
||||
});
|
||||
map.insert("separate", |language, build_ctx, function| {
|
||||
map.insert("separate", |language, diagnostics, build_ctx, function| {
|
||||
let ([separator_node], content_nodes) = function.expect_some_arguments()?;
|
||||
let separator = expect_template_expression(language, build_ctx, separator_node)?;
|
||||
let separator =
|
||||
expect_template_expression(language, diagnostics, build_ctx, separator_node)?;
|
||||
let contents = content_nodes
|
||||
.iter()
|
||||
.map(|node| expect_template_expression(language, build_ctx, node))
|
||||
.map(|node| expect_template_expression(language, diagnostics, build_ctx, node))
|
||||
.try_collect()?;
|
||||
Ok(L::wrap_template(Box::new(SeparateTemplate::new(
|
||||
separator, contents,
|
||||
))))
|
||||
});
|
||||
map.insert("surround", |language, build_ctx, function| {
|
||||
map.insert("surround", |language, diagnostics, build_ctx, function| {
|
||||
let [prefix_node, suffix_node, content_node] = function.expect_exact_arguments()?;
|
||||
let prefix = expect_template_expression(language, build_ctx, prefix_node)?;
|
||||
let suffix = expect_template_expression(language, build_ctx, suffix_node)?;
|
||||
let content = expect_template_expression(language, build_ctx, content_node)?;
|
||||
let prefix = expect_template_expression(language, diagnostics, build_ctx, prefix_node)?;
|
||||
let suffix = expect_template_expression(language, diagnostics, build_ctx, suffix_node)?;
|
||||
let content = expect_template_expression(language, diagnostics, build_ctx, content_node)?;
|
||||
let template = ReformatTemplate::new(content, move |formatter, recorded| {
|
||||
if recorded.data().is_empty() {
|
||||
return Ok(());
|
||||
|
@ -1076,6 +1178,7 @@ fn builtin_functions<'a, L: TemplateLanguage<'a> + ?Sized>() -> TemplateBuildFun
|
|||
/// Builds intermediate expression tree from AST nodes.
|
||||
pub fn build_expression<'a, L: TemplateLanguage<'a> + ?Sized>(
|
||||
language: &L,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<L::Property>,
|
||||
node: &ExpressionNode,
|
||||
) -> TemplateParseResult<Expression<L::Property>> {
|
||||
|
@ -1089,8 +1192,8 @@ pub fn build_expression<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
let make = build_ctx.self_variable;
|
||||
Ok(Expression::unlabeled(make()))
|
||||
} else {
|
||||
let property =
|
||||
build_keyword(language, build_ctx, name, node.span).map_err(|err| {
|
||||
let property = build_keyword(language, diagnostics, build_ctx, name, node.span)
|
||||
.map_err(|err| {
|
||||
err.extend_keyword_candidates(itertools::chain(
|
||||
build_ctx.local_variables.keys().copied(),
|
||||
["self"],
|
||||
|
@ -1112,29 +1215,35 @@ pub fn build_expression<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
Ok(Expression::unlabeled(property))
|
||||
}
|
||||
ExpressionKind::Unary(op, arg_node) => {
|
||||
let property = build_unary_operation(language, build_ctx, *op, arg_node)?;
|
||||
let property = build_unary_operation(language, diagnostics, build_ctx, *op, arg_node)?;
|
||||
Ok(Expression::unlabeled(property))
|
||||
}
|
||||
ExpressionKind::Binary(op, lhs_node, rhs_node) => {
|
||||
let property = build_binary_operation(language, build_ctx, *op, lhs_node, rhs_node)?;
|
||||
let property =
|
||||
build_binary_operation(language, diagnostics, build_ctx, *op, lhs_node, rhs_node)?;
|
||||
Ok(Expression::unlabeled(property))
|
||||
}
|
||||
ExpressionKind::Concat(nodes) => {
|
||||
let templates = nodes
|
||||
.iter()
|
||||
.map(|node| expect_template_expression(language, build_ctx, node))
|
||||
.map(|node| expect_template_expression(language, diagnostics, build_ctx, node))
|
||||
.try_collect()?;
|
||||
let property = L::wrap_template(Box::new(ConcatTemplate(templates)));
|
||||
Ok(Expression::unlabeled(property))
|
||||
}
|
||||
ExpressionKind::FunctionCall(function) => {
|
||||
let property = language.build_function(build_ctx, function)?;
|
||||
let property = language.build_function(diagnostics, build_ctx, function)?;
|
||||
Ok(Expression::unlabeled(property))
|
||||
}
|
||||
ExpressionKind::MethodCall(method) => {
|
||||
let mut expression = build_expression(language, build_ctx, &method.object)?;
|
||||
expression.property =
|
||||
language.build_method(build_ctx, expression.property, &method.function)?;
|
||||
let mut expression =
|
||||
build_expression(language, diagnostics, build_ctx, &method.object)?;
|
||||
expression.property = language.build_method(
|
||||
diagnostics,
|
||||
build_ctx,
|
||||
expression.property,
|
||||
&method.function,
|
||||
)?;
|
||||
expression.labels.push(method.function.name.to_owned());
|
||||
Ok(expression)
|
||||
}
|
||||
|
@ -1142,8 +1251,15 @@ pub fn build_expression<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
"Lambda cannot be defined here",
|
||||
node.span,
|
||||
)),
|
||||
ExpressionKind::AliasExpanded(id, subst) => build_expression(language, build_ctx, subst)
|
||||
.map_err(|e| e.within_alias_expansion(*id, node.span)),
|
||||
ExpressionKind::AliasExpanded(id, subst) => {
|
||||
let mut inner_diagnostics = TemplateDiagnostics::new();
|
||||
let expression = build_expression(language, &mut inner_diagnostics, build_ctx, subst)
|
||||
.map_err(|e| e.within_alias_expansion(*id, node.span))?;
|
||||
diagnostics.extend_with(inner_diagnostics, |diag| {
|
||||
diag.within_alias_expansion(*id, node.span)
|
||||
});
|
||||
Ok(expression)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1153,6 +1269,7 @@ pub fn build_expression<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
/// one of the `L::wrap_*()` functions.
|
||||
pub fn build<'a, C: Clone + 'a, L: TemplateLanguage<'a> + ?Sized>(
|
||||
language: &L,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
node: &ExpressionNode,
|
||||
// TODO: Generic L: WrapProperty<C> trait might be better. See the
|
||||
// comment in build_formattable_list_method().
|
||||
|
@ -1163,48 +1280,63 @@ pub fn build<'a, C: Clone + 'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
local_variables: HashMap::new(),
|
||||
self_variable: &|| wrap_self(self_placeholder.clone()),
|
||||
};
|
||||
let template = expect_template_expression(language, &build_ctx, node)?;
|
||||
let template = expect_template_expression(language, diagnostics, &build_ctx, node)?;
|
||||
Ok(TemplateRenderer::new(template, self_placeholder))
|
||||
}
|
||||
|
||||
/// Parses text, expands aliases, then builds template evaluation tree.
|
||||
pub fn parse<'a, C: Clone + 'a, L: TemplateLanguage<'a> + ?Sized>(
|
||||
language: &L,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
template_text: &str,
|
||||
aliases_map: &TemplateAliasesMap,
|
||||
wrap_self: impl Fn(PropertyPlaceholder<C>) -> L::Property,
|
||||
) -> TemplateParseResult<TemplateRenderer<'a, C>> {
|
||||
let node = template_parser::parse(template_text, aliases_map)?;
|
||||
build(language, &node, wrap_self).map_err(|err| err.extend_alias_candidates(aliases_map))
|
||||
build(language, diagnostics, &node, wrap_self)
|
||||
.map_err(|err| err.extend_alias_candidates(aliases_map))
|
||||
}
|
||||
|
||||
pub fn expect_boolean_expression<'a, L: TemplateLanguage<'a> + ?Sized>(
|
||||
language: &L,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<L::Property>,
|
||||
node: &ExpressionNode,
|
||||
) -> TemplateParseResult<Box<dyn TemplateProperty<Output = bool> + 'a>> {
|
||||
expect_expression_of_type(language, build_ctx, node, "Boolean", |expression| {
|
||||
expression.try_into_boolean()
|
||||
})
|
||||
expect_expression_of_type(
|
||||
language,
|
||||
diagnostics,
|
||||
build_ctx,
|
||||
node,
|
||||
"Boolean",
|
||||
|expression| expression.try_into_boolean(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn expect_integer_expression<'a, L: TemplateLanguage<'a> + ?Sized>(
|
||||
language: &L,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<L::Property>,
|
||||
node: &ExpressionNode,
|
||||
) -> TemplateParseResult<Box<dyn TemplateProperty<Output = i64> + 'a>> {
|
||||
expect_expression_of_type(language, build_ctx, node, "Integer", |expression| {
|
||||
expression.try_into_integer()
|
||||
})
|
||||
expect_expression_of_type(
|
||||
language,
|
||||
diagnostics,
|
||||
build_ctx,
|
||||
node,
|
||||
"Integer",
|
||||
|expression| expression.try_into_integer(),
|
||||
)
|
||||
}
|
||||
|
||||
/// If the given expression `node` is of `Integer` type, converts it to `isize`.
|
||||
pub fn expect_isize_expression<'a, L: TemplateLanguage<'a> + ?Sized>(
|
||||
language: &L,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<L::Property>,
|
||||
node: &ExpressionNode,
|
||||
) -> TemplateParseResult<Box<dyn TemplateProperty<Output = isize> + 'a>> {
|
||||
let i64_property = expect_integer_expression(language, build_ctx, node)?;
|
||||
let i64_property = expect_integer_expression(language, diagnostics, build_ctx, node)?;
|
||||
let isize_property = i64_property.and_then(|v| Ok(isize::try_from(v)?));
|
||||
Ok(Box::new(isize_property))
|
||||
}
|
||||
|
@ -1212,48 +1344,74 @@ pub fn expect_isize_expression<'a, L: TemplateLanguage<'a> + ?Sized>(
|
|||
/// If the given expression `node` is of `Integer` type, converts it to `usize`.
|
||||
pub fn expect_usize_expression<'a, L: TemplateLanguage<'a> + ?Sized>(
|
||||
language: &L,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<L::Property>,
|
||||
node: &ExpressionNode,
|
||||
) -> TemplateParseResult<Box<dyn TemplateProperty<Output = usize> + 'a>> {
|
||||
let i64_property = expect_integer_expression(language, build_ctx, node)?;
|
||||
let i64_property = expect_integer_expression(language, diagnostics, build_ctx, node)?;
|
||||
let usize_property = i64_property.and_then(|v| Ok(usize::try_from(v)?));
|
||||
Ok(Box::new(usize_property))
|
||||
}
|
||||
|
||||
pub fn expect_plain_text_expression<'a, L: TemplateLanguage<'a> + ?Sized>(
|
||||
language: &L,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<L::Property>,
|
||||
node: &ExpressionNode,
|
||||
) -> TemplateParseResult<Box<dyn TemplateProperty<Output = String> + 'a>> {
|
||||
// Since any formattable type can be converted to a string property,
|
||||
// the expected type is not a String, but a Template.
|
||||
expect_expression_of_type(language, build_ctx, node, "Template", |expression| {
|
||||
expression.try_into_plain_text()
|
||||
})
|
||||
expect_expression_of_type(
|
||||
language,
|
||||
diagnostics,
|
||||
build_ctx,
|
||||
node,
|
||||
"Template",
|
||||
|expression| expression.try_into_plain_text(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn expect_template_expression<'a, L: TemplateLanguage<'a> + ?Sized>(
|
||||
language: &L,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<L::Property>,
|
||||
node: &ExpressionNode,
|
||||
) -> TemplateParseResult<Box<dyn Template + 'a>> {
|
||||
expect_expression_of_type(language, build_ctx, node, "Template", |expression| {
|
||||
expression.try_into_template()
|
||||
})
|
||||
expect_expression_of_type(
|
||||
language,
|
||||
diagnostics,
|
||||
build_ctx,
|
||||
node,
|
||||
"Template",
|
||||
|expression| expression.try_into_template(),
|
||||
)
|
||||
}
|
||||
|
||||
fn expect_expression_of_type<'a, L: TemplateLanguage<'a> + ?Sized, T>(
|
||||
language: &L,
|
||||
diagnostics: &mut TemplateDiagnostics,
|
||||
build_ctx: &BuildContext<L::Property>,
|
||||
node: &ExpressionNode,
|
||||
expected_type: &str,
|
||||
f: impl FnOnce(Expression<L::Property>) -> Option<T>,
|
||||
) -> TemplateParseResult<T> {
|
||||
if let ExpressionKind::AliasExpanded(id, subst) = &node.kind {
|
||||
expect_expression_of_type(language, build_ctx, subst, expected_type, f)
|
||||
.map_err(|e| e.within_alias_expansion(*id, node.span))
|
||||
let mut inner_diagnostics = TemplateDiagnostics::new();
|
||||
let expression = expect_expression_of_type(
|
||||
language,
|
||||
&mut inner_diagnostics,
|
||||
build_ctx,
|
||||
subst,
|
||||
expected_type,
|
||||
f,
|
||||
)
|
||||
.map_err(|e| e.within_alias_expansion(*id, node.span))?;
|
||||
diagnostics.extend_with(inner_diagnostics, |diag| {
|
||||
diag.within_alias_expansion(*id, node.span)
|
||||
});
|
||||
Ok(expression)
|
||||
} else {
|
||||
let expression = build_expression(language, build_ctx, node)?;
|
||||
let expression = build_expression(language, diagnostics, build_ctx, node)?;
|
||||
let actual_type = expression.type_name();
|
||||
f(expression)
|
||||
.ok_or_else(|| TemplateParseError::expected_type(expected_type, actual_type, node.span))
|
||||
|
@ -1313,7 +1471,13 @@ mod tests {
|
|||
}
|
||||
|
||||
fn parse(&self, template: &str) -> TemplateParseResult<TemplateRenderer<'static, ()>> {
|
||||
parse(&self.language, template, &self.aliases_map, L::wrap_self)
|
||||
parse(
|
||||
&self.language,
|
||||
&mut TemplateDiagnostics::new(),
|
||||
template,
|
||||
&self.aliases_map,
|
||||
L::wrap_self,
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_err(&self, template: &str) -> String {
|
||||
|
|
|
@ -26,6 +26,7 @@ use jj_lib::dsl_util::AliasExpandError;
|
|||
use jj_lib::dsl_util::AliasExpandableExpression;
|
||||
use jj_lib::dsl_util::AliasId;
|
||||
use jj_lib::dsl_util::AliasesMap;
|
||||
use jj_lib::dsl_util::Diagnostics;
|
||||
use jj_lib::dsl_util::ExpressionFolder;
|
||||
use jj_lib::dsl_util::FoldableExpression;
|
||||
use jj_lib::dsl_util::InvalidArguments;
|
||||
|
@ -84,6 +85,9 @@ impl Rule {
|
|||
}
|
||||
}
|
||||
|
||||
/// Manages diagnostic messages emitted during template parsing and building.
|
||||
pub type TemplateDiagnostics = Diagnostics<TemplateParseError>;
|
||||
|
||||
pub type TemplateParseResult<T> = Result<T, TemplateParseError>;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
|
Loading…
Reference in a new issue