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

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	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(&regex, 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(&regex, 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}