1use std::{cell::RefCell, num::NonZeroUsize, rc::Rc};23use ::regex::Regex;4use jrsonnet_evaluator::{5 IStr, ObjValue, ObjValueBuilder,6 error::{ErrorKind::*, Result},7 rustc_hash::FxBuildHasher,8 typed::{IntoUntyped, Typed},9 val::StrValue,10};11use jrsonnet_gcmodule::Acyclic;12use jrsonnet_macros::builtin;13use lru::LruCache;1415#[derive(Acyclic)]16pub struct RegexCacheInner {17 cache: RefCell<LruCache<IStr, Rc<Regex>, FxBuildHasher>>,18}19impl Default for RegexCacheInner {20 fn default() -> Self {21 Self {22 cache: RefCell::new(LruCache::with_hasher(23 NonZeroUsize::new(20).unwrap(),24 FxBuildHasher,25 )),26 }27 }28}29pub type RegexCache = Rc<RegexCacheInner>;30impl RegexCacheInner {31 fn parse(&self, pattern: IStr) -> Result<Rc<Regex>> {32 let mut cache = self.cache.borrow_mut();33 if let Some(found) = cache.get(&pattern) {34 return Ok(found.clone());35 }36 let regex = Regex::new(&pattern)37 .map_err(|e| RuntimeError(format!("regex parse failed: {e}").into()))?;38 let regex = Rc::new(regex);39 cache.push(pattern, regex.clone());40 Ok(regex)41 }42}4344#[derive(Typed, IntoUntyped)]45pub struct RegexMatch {46 string: IStr,47 captures: Vec<IStr>,48 #[typed(rename = "namedCaptures")]49 named_captures: ObjValue,50}5152fn regex_match_inner(regex: &Regex, str: String) -> Result<Option<RegexMatch>> {53 let mut captures = Vec::with_capacity(regex.captures_len());54 let mut named_captures = ObjValueBuilder::with_capacity(regex.capture_names().len());5556 let Some(captured) = regex.captures(&str) else {57 return Ok(None);58 };5960 for ele in captured.iter().skip(1) {61 if let Some(ele) = ele {62 captures.push(ele.as_str().into());63 } else {64 captures.push(IStr::empty());65 }66 }67 for (i, name) in regex68 .capture_names()69 .skip(1)70 .enumerate()71 .filter_map(|(i, v)| Some((i, v?)))72 {73 let capture = captures[i].clone();74 named_captures.field(name).try_value(capture)?;75 }7677 Ok(Some(RegexMatch {78 string: captured.get(0).expect("regex matched").as_str().into(),79 named_captures: named_captures.build(),80 captures,81 }))82}8384#[builtin(fields(85 cache: RegexCache,86))]87pub fn builtin_regex_partial_match(88 this: &builtin_regex_partial_match,89 pattern: IStr,90 str: String,91) -> Result<Option<RegexMatch>> {92 let regex = this.cache.parse(pattern)?;93 regex_match_inner(®ex, str)94}9596#[builtin(fields(97 cache: RegexCache,98))]99pub fn builtin_regex_full_match(100 this: &builtin_regex_full_match,101 pattern: StrValue,102 str: String,103) -> Result<Option<RegexMatch>> {104 let pattern = format!("^{pattern}$").into();105 let regex = this.cache.parse(pattern)?;106 regex_match_inner(®ex, str)107}108109#[builtin]110pub fn builtin_regex_quote_meta(pattern: String) -> String {111 regex::escape(&pattern)112}113114#[builtin(fields(115 cache: RegexCache,116))]117pub fn builtin_regex_replace(118 this: &builtin_regex_replace,119 str: String,120 pattern: IStr,121 to: String,122) -> Result<String> {123 let regex = this.cache.parse(pattern)?;124 let replaced = regex.replace(&str, to);125 Ok(replaced.to_string())126}127128#[builtin(fields(129 cache: RegexCache,130))]131pub fn builtin_regex_global_replace(132 this: &builtin_regex_global_replace,133 str: String,134 pattern: IStr,135 to: String,136) -> Result<String> {137 let regex = this.cache.parse(pattern)?;138 let replaced = regex.replace_all(&str, to);139 Ok(replaced.to_string())140}