git.delta.rocks / jrsonnet / refs/commits / 0111266c91b4

difftreelog

source

crates/jrsonnet-stdlib/src/regex.rs3.2 KiBsourcehistory
1use std::{cell::RefCell, num::NonZeroUsize, rc::Rc};23use ::regex::Regex;4use jrsonnet_evaluator::{5	error::{ErrorKind::*, Result},6	rustc_hash::FxBuildHasher,7	val::StrValue,8	IStr, ObjValueBuilder, Val,9};10use jrsonnet_gcmodule::Acyclic;11use jrsonnet_macros::builtin;12use lru::LruCache;1314#[derive(Acyclic)]15pub struct RegexCacheInner {16	cache: RefCell<LruCache<IStr, Rc<Regex>, FxBuildHasher>>,17}18impl Default for RegexCacheInner {19	fn default() -> Self {20		Self {21			cache: RefCell::new(LruCache::with_hasher(22				NonZeroUsize::new(20).unwrap(),23				FxBuildHasher::default(),24			)),25		}26	}27}28pub type RegexCache = Rc<RegexCacheInner>;29impl RegexCacheInner {30	fn parse(&self, pattern: IStr) -> Result<Rc<Regex>> {31		let mut cache = self.cache.borrow_mut();32		if let Some(found) = cache.get(&pattern) {33			return Ok(found.clone());34		}35		let regex = Regex::new(&pattern)36			.map_err(|e| RuntimeError(format!("regex parse failed: {e}").into()))?;37		let regex = Rc::new(regex);38		cache.push(pattern, regex.clone());39		Ok(regex)40	}41}4243pub fn regex_match_inner(regex: &Regex, str: String) -> Result<Val> {44	let mut out = ObjValueBuilder::with_capacity(3);4546	let mut captures = Vec::with_capacity(regex.captures_len());47	let mut named_captures = ObjValueBuilder::with_capacity(regex.capture_names().len());4849	let Some(captured) = regex.captures(&str) else {50		return Ok(Val::Null);51	};5253	for ele in captured.iter().skip(1) {54		if let Some(ele) = ele {55			captures.push(Val::Str(StrValue::Flat(ele.as_str().into())));56		} else {57			captures.push(Val::Str(StrValue::Flat(IStr::empty())));58		}59	}60	for (i, name) in regex61		.capture_names()62		.skip(1)63		.enumerate()64		.filter_map(|(i, v)| Some((i, v?)))65	{66		let capture = captures[i].clone();67		named_captures.field(name).try_value(capture)?;68	}6970	out.field("string")71		.value(Val::Str(captured.get(0).unwrap().as_str().into()));72	out.field("captures").value(Val::Arr(captures.into()));73	out.field("namedCaptures")74		.value(Val::Obj(named_captures.build()));7576	Ok(Val::Obj(out.build()))77}7879#[builtin(fields(80    cache: RegexCache,81))]82pub fn builtin_regex_partial_match(83	this: &builtin_regex_partial_match,84	pattern: IStr,85	str: String,86) -> Result<Val> {87	let regex = this.cache.parse(pattern)?;88	regex_match_inner(&regex, str)89}9091#[builtin(fields(92    cache: RegexCache,93))]94pub fn builtin_regex_full_match(95	this: &builtin_regex_full_match,96	pattern: StrValue,97	str: String,98) -> Result<Val> {99	let pattern = format!("^{pattern}$").into();100	let regex = this.cache.parse(pattern)?;101	regex_match_inner(&regex, str)102}103104#[builtin]105pub fn builtin_regex_quote_meta(pattern: String) -> String {106	regex::escape(&pattern)107}108109#[builtin(fields(110    cache: RegexCache,111))]112pub fn builtin_regex_replace(113	this: &builtin_regex_replace,114	str: String,115	pattern: IStr,116	to: String,117) -> Result<String> {118	let regex = this.cache.parse(pattern)?;119	let replaced = regex.replace(&str, to);120	Ok(replaced.to_string())121}122123#[builtin(fields(124    cache: RegexCache,125))]126pub fn builtin_regex_global_replace(127	this: &builtin_regex_global_replace,128	str: String,129	pattern: IStr,130	to: String,131) -> Result<String> {132	let regex = this.cache.parse(pattern)?;133	let replaced = regex.replace_all(&str, to);134	Ok(replaced.to_string())135}