From 7ef30e1aae5b9d2b6d9b73fba41efc2b22c43b4f Mon Sep 17 00:00:00 2001 From: euzu Date: Mon, 21 Jul 2025 22:39:23 +0200 Subject: [PATCH] Added replace built-in function to mapper DSL --- CHANGELOG.md | 5 ++++ README.md | 5 ++++ shared/src/foundation/mapper.pest | 2 +- shared/src/foundation/mapper.rs | 42 +++++++++++++++++++++++-------- 4 files changed, 43 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 497c4e2d0..5999ae9a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ # Changelog +- Hot reload for config +- Fixed shared stream provider connection count +- Added hanging client connection release +- Added `replace` built-in function for mapper scripts + # 3.1.4 (2025-06-17) - share live stream refactored - fixed active user count diff --git a/README.md b/README.md index 4b5ab75b3..2a7f38e30 100644 --- a/README.md +++ b/README.md @@ -1115,6 +1115,7 @@ It is whitespace-tolerant and uses familiar programming concepts with a custom s - print(a, b, c) - first(a) - template(a) + - replace(text, match, replacement) Field names are: `name`, `title"`, `caption"`, `group"`, `id"`, `chno"`, `logo"`, `logo_small"`, `parent_code"`, `time_shift" | "url"`, `epg_channel_id"`, `epg_id`. When you use Regular expressions it could be that your match contains multiple results. The builtin function `first` returns the first match. Example `print(uppercase("hello"))`. output is only visible in `trace` log level you can enable it like `log_level: debug,tuliprox::foundation::mapper=trace` in config @@ -1182,6 +1183,10 @@ Example `if then else` block } ``` +Example of removing prefix +``` +`@Caption = replace(@Caption, "UK:", "EN:"` +``` Example `mapping.yml` diff --git a/shared/src/foundation/mapper.pest b/shared/src/foundation/mapper.pest index eb4fa2f28..f963ec4e5 100644 --- a/shared/src/foundation/mapper.pest +++ b/shared/src/foundation/mapper.pest @@ -18,7 +18,7 @@ block_expr = { "{" ~ statements ~ "}" } condition = { function_call | var_access | field_access } assignment = { (field_access | identifier) ~ "=" ~ expression } expression = { assignment | map_block | match_block | function_call | regex_expr | string_literal | number | var_access | field_access | null | block_expr } -function_name = { "concat" | "uppercase" | "lowercase" | "capitalize" | "trim" | "print" | "number" | "first" | "template" } +function_name = { "concat" | "uppercase" | "lowercase" | "capitalize" | "trim" | "print" | "number" | "first" | "template" | "replace" } function_call = { function_name ~ "(" ~ (expression ~ ("," ~ expression)*)? ~ ")" } any_match = { "_" } match_case_key = { any_match | identifier } diff --git a/shared/src/foundation/mapper.rs b/shared/src/foundation/mapper.rs index 778c12bbe..60e05bfc4 100644 --- a/shared/src/foundation/mapper.rs +++ b/shared/src/foundation/mapper.rs @@ -37,7 +37,7 @@ block_expr = { "{" ~ statements ~ "}" } condition = { function_call | var_access | field_access } assignment = { (field_access | identifier) ~ "=" ~ expression } expression = { assignment | map_block | match_block | function_call | regex_expr | string_literal | number | var_access | field_access | null | block_expr } -function_name = { "concat" | "uppercase" | "lowercase" | "capitalize" | "trim" | "print" | "number" | "first" | "template" } +function_name = { "concat" | "uppercase" | "lowercase" | "capitalize" | "trim" | "print" | "number" | "first" | "template" | "replace" } function_call = { function_name ~ "(" ~ (expression ~ ("," ~ expression)*)? ~ ")" } any_match = { "_" } match_case_key = { any_match | identifier } @@ -107,6 +107,7 @@ enum BuiltInFunction { ToNumber, First, Template, + Replace, } impl FromStr for BuiltInFunction { @@ -123,6 +124,7 @@ impl FromStr for BuiltInFunction { "number" => Ok(Self::ToNumber), "first" => Ok(Self::First), "template" => Ok(Self::Template), + "replace" => Ok(Self::Replace), _ => create_tuliprox_error_result!(TuliproxErrorKind::Info, "Unknown function {}", s), } } @@ -659,8 +661,13 @@ impl<'a> MapperContext<'a> { if args.len() > 1 { return create_tuliprox_error_result!(TuliproxErrorKind::Info, "Function accepts only one argument {:?}, {} given", name, args.len()); } + }, + BuiltInFunction::Replace => { + if args.len() != 3 { + return create_tuliprox_error_result!(TuliproxErrorKind::Info, "Function accepts three arguments {:?}, {} given", name, args.len()); + } } - _ => {} + _ => {} } for expr_id in args { self.validate_expr(*expr_id, identifiers)?; @@ -887,6 +894,17 @@ fn concat_args(args: &Vec) -> Vec> { result } +macro_rules! extract_evaluated_arg_value { + ($evaluated_args:expr, $index:expr) => {{ + let evaluated_arg = &$evaluated_args[$index]; + match evaluated_arg { + Value(value) => Some(value), + Named(values) => values.first().map(|(_key, val)| val), + _ => None, + } + }}; +} + impl Expression { #[allow(clippy::too_many_lines)] pub fn eval(&self, ctx: &mut MapperContext, accessor: &mut ValueAccessor) -> EvalResult { @@ -1048,14 +1066,7 @@ impl Expression { } } BuiltInFunction::Template => { - let evaluated_arg = &evaluated_args[0]; - let value = match evaluated_arg { - Value(value) => { - Some(value) - } - Named(values) => values.first().map(|(_key, val)| val), - _ => None - }; + let value = extract_evaluated_arg_value!(evaluated_args, 0); if let Some(val) = value { match ctx.get_template(val) { Some(v) => Value(v.to_string()), @@ -1065,6 +1076,17 @@ impl Expression { Undefined } } + BuiltInFunction::Replace => { + let value = extract_evaluated_arg_value!(evaluated_args, 0); + let pattern = extract_evaluated_arg_value!(evaluated_args, 1); + let substring = extract_evaluated_arg_value!(evaluated_args, 2); + + if let (Some(text), Some(pat), Some(subst)) = (value, pattern, substring) { + Value(text.replace(pat, subst)) + } else { + evaluated_args[0].clone() + } + } } } }