just playing with tangled
1// Copyright 2024 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::any::Any;
16use std::rc::Rc;
17
18use itertools::Itertools as _;
19use jj_cli::cli_util::CliRunner;
20use jj_cli::commit_templater::CommitTemplateBuildFnTable;
21use jj_cli::commit_templater::CommitTemplateLanguage;
22use jj_cli::commit_templater::CommitTemplateLanguageExtension;
23use jj_cli::template_builder::TemplateLanguage as _;
24use jj_cli::template_parser;
25use jj_cli::template_parser::TemplateParseError;
26use jj_cli::templater::TemplatePropertyExt as _;
27use jj_lib::backend::CommitId;
28use jj_lib::commit::Commit;
29use jj_lib::extensions_map::ExtensionsMap;
30use jj_lib::object_id::ObjectId as _;
31use jj_lib::repo::Repo;
32use jj_lib::revset::FunctionCallNode;
33use jj_lib::revset::LoweringContext;
34use jj_lib::revset::PartialSymbolResolver;
35use jj_lib::revset::RevsetDiagnostics;
36use jj_lib::revset::RevsetExpression;
37use jj_lib::revset::RevsetFilterExtension;
38use jj_lib::revset::RevsetFilterPredicate;
39use jj_lib::revset::RevsetParseError;
40use jj_lib::revset::RevsetResolutionError;
41use jj_lib::revset::SymbolResolverExtension;
42use jj_lib::revset::UserRevsetExpression;
43use once_cell::sync::OnceCell;
44
45struct HexCounter;
46
47fn num_digits_in_id(id: &CommitId) -> i64 {
48 let mut count = 0;
49 for ch in id.hex().chars() {
50 if ch.is_ascii_digit() {
51 count += 1;
52 }
53 }
54 count
55}
56
57fn num_char_in_id(commit: Commit, ch_match: char) -> i64 {
58 let mut count = 0;
59 for ch in commit.id().hex().chars() {
60 if ch == ch_match {
61 count += 1;
62 }
63 }
64 count
65}
66
67#[derive(Default)]
68struct MostDigitsInId {
69 count: OnceCell<i64>,
70}
71
72impl MostDigitsInId {
73 fn count(&self, repo: &dyn Repo) -> i64 {
74 *self.count.get_or_init(|| {
75 RevsetExpression::all()
76 .evaluate(repo)
77 .unwrap()
78 .iter()
79 .map(Result::unwrap)
80 .map(|id| num_digits_in_id(&id))
81 .max()
82 .unwrap_or(0)
83 })
84 }
85}
86
87#[derive(Default)]
88struct TheDigitestResolver {
89 cache: MostDigitsInId,
90}
91
92impl PartialSymbolResolver for TheDigitestResolver {
93 fn resolve_symbol(
94 &self,
95 repo: &dyn Repo,
96 symbol: &str,
97 ) -> Result<Option<Vec<CommitId>>, RevsetResolutionError> {
98 if symbol != "thedigitest" {
99 return Ok(None);
100 }
101
102 Ok(Some(
103 RevsetExpression::all()
104 .evaluate(repo)
105 .map_err(|err| RevsetResolutionError::Other(err.into()))?
106 .iter()
107 .map(Result::unwrap)
108 .filter(|id| num_digits_in_id(id) == self.cache.count(repo))
109 .collect_vec(),
110 ))
111 }
112}
113
114struct TheDigitest;
115
116impl SymbolResolverExtension for TheDigitest {
117 fn new_resolvers<'a>(&self, _repo: &'a dyn Repo) -> Vec<Box<dyn PartialSymbolResolver + 'a>> {
118 vec![Box::<TheDigitestResolver>::default()]
119 }
120}
121
122impl CommitTemplateLanguageExtension for HexCounter {
123 fn build_fn_table<'repo>(&self) -> CommitTemplateBuildFnTable<'repo> {
124 type L<'repo> = CommitTemplateLanguage<'repo>;
125 let mut table = CommitTemplateBuildFnTable::empty();
126 table.commit_methods.insert(
127 "has_most_digits",
128 |language, _diagnostics, _build_context, property, call| {
129 call.expect_no_arguments()?;
130 let most_digits = language
131 .cache_extension::<MostDigitsInId>()
132 .unwrap()
133 .count(language.repo());
134 Ok(L::wrap_boolean(property.map(move |commit| {
135 num_digits_in_id(commit.id()) == most_digits
136 })))
137 },
138 );
139 table.commit_methods.insert(
140 "num_digits_in_id",
141 |_language, _diagnostics, _build_context, property, call| {
142 call.expect_no_arguments()?;
143 Ok(L::wrap_integer(
144 property.map(|commit| num_digits_in_id(commit.id())),
145 ))
146 },
147 );
148 table.commit_methods.insert(
149 "num_char_in_id",
150 |_language, _diagnostics, _build_context, property, call| {
151 let [string_arg] = call.expect_exact_arguments()?;
152 let char_arg =
153 template_parser::expect_string_literal_with(string_arg, |string, span| {
154 let chars: Vec<_> = string.chars().collect();
155 match chars[..] {
156 [ch] => Ok(ch),
157 _ => Err(TemplateParseError::expression(
158 "Expected singular character argument",
159 span,
160 )),
161 }
162 })?;
163
164 Ok(L::wrap_integer(
165 property.map(move |commit| num_char_in_id(commit, char_arg)),
166 ))
167 },
168 );
169
170 table
171 }
172
173 fn build_cache_extensions(&self, extensions: &mut ExtensionsMap) {
174 extensions.insert(MostDigitsInId::default());
175 }
176}
177
178#[derive(Debug)]
179struct EvenDigitsFilter;
180
181impl RevsetFilterExtension for EvenDigitsFilter {
182 fn as_any(&self) -> &dyn Any {
183 self
184 }
185
186 fn matches_commit(&self, commit: &Commit) -> bool {
187 num_digits_in_id(commit.id()) % 2 == 0
188 }
189}
190
191fn even_digits(
192 _diagnostics: &mut RevsetDiagnostics,
193 function: &FunctionCallNode,
194 _context: &LoweringContext,
195) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
196 function.expect_no_arguments()?;
197 Ok(RevsetExpression::filter(RevsetFilterPredicate::Extension(
198 Rc::new(EvenDigitsFilter),
199 )))
200}
201
202fn main() -> std::process::ExitCode {
203 CliRunner::init()
204 .add_symbol_resolver_extension(Box::new(TheDigitest))
205 .add_revset_function_extension("even_digits", even_digits)
206 .add_commit_template_extension(Box::new(HexCounter))
207 .run()
208}