git.delta.rocks / jrsonnet / refs/commits / dbbcbc4becf1

difftreelog

source

crates/jrsonnet-stdlib/src/regex.rs3.2 KiBsourcehistory
1use std::{cell::RefCell, hash::BuildHasherDefault, num::NonZeroUsize, rc::Rc};23use ::regex::Regex;4use jrsonnet_evaluator::{5	error::{ErrorKind::*, Result},6	val::StrValue,7	IStr, ObjValueBuilder, Val,8};9use jrsonnet_macros::builtin;10use lru::LruCache;11use rustc_hash::FxHasher;1213pub struct RegexCacheInner {14	cache: RefCell<LruCache<IStr, Rc<Regex>, BuildHasherDefault<FxHasher>>>,15}16impl Default for RegexCacheInner {17	fn default() -> Self {18		Self {19			cache: RefCell::new(LruCache::with_hasher(20				NonZeroUsize::new(20).unwrap(),21				BuildHasherDefault::default(),22			)),23		}24	}25}26pub type RegexCache = Rc<RegexCacheInner>;27impl RegexCacheInner {28	fn parse(&self, pattern: IStr) -> Result<Rc<Regex>> {29		let mut cache = self.cache.borrow_mut();30		if let Some(found) = cache.get(&pattern) {31			return Ok(found.clone());32		}33		let regex = Regex::new(&pattern)34			.map_err(|e| RuntimeError(format!("regex parse failed: {e}").into()))?;35		let regex = Rc::new(regex);36		cache.push(pattern, regex.clone());37		Ok(regex)38	}39}4041pub fn regex_match_inner(regex: &Regex, str: String) -> Result<Val> {42	let mut out = ObjValueBuilder::with_capacity(3);4344	let mut captures = Vec::with_capacity(regex.captures_len());45	let mut named_captures = ObjValueBuilder::with_capacity(regex.capture_names().len());4647	let Some(captured) = regex.captures(&str) else {48		return Ok(Val::Null);49	};5051	for ele in captured.iter().skip(1) {52		if let Some(ele) = ele {53			captures.push(Val::Str(StrValue::Flat(ele.as_str().into())))54		} else {55			captures.push(Val::Str(StrValue::Flat(IStr::empty())))56		}57	}58	for (i, name) in regex59		.capture_names()60		.skip(1)61		.enumerate()62		.flat_map(|(i, v)| Some((i, v?)))63	{64		let capture = captures[i].clone();65		named_captures.field(name).try_value(capture)?;66	}6768	out.field("string")69		.value(Val::Str(captured.get(0).unwrap().as_str().into()));70	out.field("captures").value(Val::Arr(captures.into()));71	out.field("namedCaptures")72		.value(Val::Obj(named_captures.build()));7374	Ok(Val::Obj(out.build()))75}7677#[builtin(fields(78    cache: RegexCache,79))]80pub fn builtin_regex_partial_match(81	this: &builtin_regex_partial_match,82	pattern: IStr,83	str: String,84) -> Result<Val> {85	let regex = this.cache.parse(pattern)?;86	regex_match_inner(&regex, str)87}8889#[builtin(fields(90    cache: RegexCache,91))]92pub fn builtin_regex_full_match(93	this: &builtin_regex_full_match,94	pattern: StrValue,95	str: String,96) -> Result<Val> {97	let pattern = format!("^{pattern}$").into();98	let regex = this.cache.parse(pattern)?;99	regex_match_inner(&regex, str)100}101102#[builtin]103pub fn builtin_regex_quote_meta(pattern: String) -> String {104	regex::escape(&pattern)105}106107#[builtin(fields(108    cache: RegexCache,109))]110pub fn builtin_regex_replace(111	this: &builtin_regex_replace,112	str: String,113	pattern: IStr,114	to: String,115) -> Result<String> {116	let regex = this.cache.parse(pattern)?;117	let replaced = regex.replace(&str, to);118	Ok(replaced.to_string())119}120121#[builtin(fields(122    cache: RegexCache,123))]124pub fn builtin_regex_global_replace(125	this: &builtin_regex_global_replace,126	str: String,127	pattern: IStr,128	to: String,129) -> Result<String> {130	let regex = this.cache.parse(pattern)?;131	let replaced = regex.replace_all(&str, to);132	Ok(replaced.to_string())133}