just playing with tangled
at ig/vimdiffwarn 208 lines 6.5 kB view raw
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}