mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-09 01:12:23 +02:00
Mapper script built-in function template
This commit is contained in:
@@ -6,6 +6,13 @@
|
||||
- more logs sanitized
|
||||
- added session key for session management
|
||||
- added sleep timer `sleep_timer_mins` to config.yml
|
||||
- added mapper script builtin function `template` to access template definitions.
|
||||
```
|
||||
station_prefix = template(concat("US_", station, "_PREFIX")),
|
||||
```
|
||||
If we assume the variable `station` contains the value `WINK`,
|
||||
this script receives the template with the concatenated name `US_WINK_PREFIX` which should be defined in `templates` section,
|
||||
and assigns it to the variable `station_prefix`.
|
||||
|
||||
# 3.1.3 (2025-06-06)
|
||||
- Fixed xtream codes series info duplicate fields problem.
|
||||
|
||||
+42
-43
@@ -8,7 +8,6 @@ use pest::iterators::Pair;
|
||||
use pest::Parser;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::model::{FieldGetAccessor, FieldSetAccessor, ItemField};
|
||||
use crate::model::{PlaylistItem, PlaylistItemType};
|
||||
use crate::tools::directed_graph::DirectedGraph;
|
||||
@@ -286,7 +285,7 @@ fn get_parser_item_field(expr: &Pair<Rule>) -> Result<ItemField, TuliproxError>
|
||||
|
||||
fn get_parser_regexp(
|
||||
expr: &Pair<Rule>,
|
||||
templates: &Vec<PatternTemplate>,
|
||||
templates: Option<&Vec<PatternTemplate>>,
|
||||
) -> Result<CompiledRegex, TuliproxError> {
|
||||
if expr.as_rule() == Rule::regexp {
|
||||
let mut parsed_text = String::from(expr.as_str());
|
||||
@@ -311,7 +310,7 @@ fn get_parser_regexp(
|
||||
|
||||
fn get_parser_field_comparison(
|
||||
expr: Pair<Rule>,
|
||||
templates: &Vec<PatternTemplate>,
|
||||
templates: Option<&Vec<PatternTemplate>>,
|
||||
) -> Result<Filter, TuliproxError> {
|
||||
let mut expr_inner = expr.into_inner();
|
||||
match get_parser_item_field(&expr_inner.next().unwrap()) {
|
||||
@@ -372,7 +371,7 @@ macro_rules! handle_expr {
|
||||
|
||||
fn get_parser_expression(
|
||||
expr: Pair<Rule>,
|
||||
templates: &Vec<PatternTemplate>,
|
||||
templates: Option<&Vec<PatternTemplate>>,
|
||||
errors: &mut Vec<String>,
|
||||
) -> Result<Filter, String> {
|
||||
let mut stmts = Vec::with_capacity(128);
|
||||
@@ -450,9 +449,7 @@ pub fn get_filter(
|
||||
filter_text: &str,
|
||||
templates: Option<&Vec<PatternTemplate>>,
|
||||
) -> Result<Filter, TuliproxError> {
|
||||
let empty_list = Vec::with_capacity(0);
|
||||
let template_list: &Vec<PatternTemplate> = templates.unwrap_or(&empty_list);
|
||||
let source = apply_templates_to_pattern_single(filter_text, template_list)?;
|
||||
let source = apply_templates_to_pattern_single(filter_text, templates)?;
|
||||
|
||||
match FilterParser::parse(Rule::main, &source) {
|
||||
Ok(pairs) => {
|
||||
@@ -465,7 +462,7 @@ pub fn get_filter(
|
||||
for expr in pair.into_inner() {
|
||||
match expr.as_rule() {
|
||||
Rule::expr => {
|
||||
match get_parser_expression(expr, template_list, &mut errors) {
|
||||
match get_parser_expression(expr, templates, &mut errors) {
|
||||
Ok(expr) => {
|
||||
match &op {
|
||||
Some(binop) => {
|
||||
@@ -652,59 +649,61 @@ pub fn prepare_templates(templates: &mut Vec<PatternTemplate>) -> Result<Vec<Pat
|
||||
|
||||
pub fn apply_templates_to_pattern(
|
||||
pattern: &str,
|
||||
templates: &Vec<PatternTemplate>,
|
||||
templates_list: Option<&Vec<PatternTemplate>>,
|
||||
allow_multi: bool,
|
||||
) -> Result<TemplateValue, TuliproxError> {
|
||||
let mut new_pattern = TemplateValue::Single(pattern.to_string());
|
||||
|
||||
for template in templates {
|
||||
match &template.value {
|
||||
TemplateValue::Single(val) => {
|
||||
match new_pattern {
|
||||
TemplateValue::Single(ref mut pat) => {
|
||||
let replaced = pat.replace(&template.placeholder, val);
|
||||
if replaced != *pat {
|
||||
*pat = replaced;
|
||||
}
|
||||
}
|
||||
TemplateValue::Multi(ref mut pats) => {
|
||||
for pat in pats.iter_mut() {
|
||||
if let Some(templates) = templates_list {
|
||||
for template in templates {
|
||||
match &template.value {
|
||||
TemplateValue::Single(val) => {
|
||||
match new_pattern {
|
||||
TemplateValue::Single(ref mut pat) => {
|
||||
let replaced = pat.replace(&template.placeholder, val);
|
||||
if replaced != *pat {
|
||||
*pat = replaced;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TemplateValue::Multi(ref multi_vals) => {
|
||||
new_pattern = match &new_pattern {
|
||||
TemplateValue::Single(pat) => {
|
||||
let mut new_values = IndexSet::new();
|
||||
for val in multi_vals {
|
||||
if pat.contains(&template.placeholder) {
|
||||
new_values.insert(pat.replace(&template.placeholder, val));
|
||||
} else {
|
||||
new_values.insert(pat.clone());
|
||||
TemplateValue::Multi(ref mut pats) => {
|
||||
for pat in pats.iter_mut() {
|
||||
let replaced = pat.replace(&template.placeholder, val);
|
||||
if replaced != *pat {
|
||||
*pat = replaced;
|
||||
}
|
||||
}
|
||||
}
|
||||
TemplateValue::Multi(new_values.into_iter().collect())
|
||||
}
|
||||
TemplateValue::Multi(pats) => {
|
||||
let mut new_values = IndexSet::new();
|
||||
for val in multi_vals {
|
||||
for pat in pats {
|
||||
}
|
||||
|
||||
TemplateValue::Multi(ref multi_vals) => {
|
||||
new_pattern = match &new_pattern {
|
||||
TemplateValue::Single(pat) => {
|
||||
let mut new_values = IndexSet::new();
|
||||
for val in multi_vals {
|
||||
if pat.contains(&template.placeholder) {
|
||||
new_values.insert(pat.replace(&template.placeholder, val));
|
||||
} else {
|
||||
new_values.insert(pat.clone());
|
||||
}
|
||||
}
|
||||
TemplateValue::Multi(new_values.into_iter().collect())
|
||||
}
|
||||
TemplateValue::Multi(new_values.into_iter().collect())
|
||||
}
|
||||
};
|
||||
TemplateValue::Multi(pats) => {
|
||||
let mut new_values = IndexSet::new();
|
||||
for val in multi_vals {
|
||||
for pat in pats {
|
||||
if pat.contains(&template.placeholder) {
|
||||
new_values.insert(pat.replace(&template.placeholder, val));
|
||||
} else {
|
||||
new_values.insert(pat.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
TemplateValue::Multi(new_values.into_iter().collect())
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -731,7 +730,7 @@ pub fn apply_templates_to_pattern(
|
||||
Ok(new_pattern)
|
||||
}
|
||||
|
||||
pub fn apply_templates_to_pattern_single(pattern: &str, templates: &Vec<PatternTemplate>) -> Result<String, TuliproxError> {
|
||||
pub fn apply_templates_to_pattern_single(pattern: &str, templates: Option<&Vec<PatternTemplate>>) -> Result<String, TuliproxError> {
|
||||
match apply_templates_to_pattern(pattern, templates, false)? {
|
||||
TemplateValue::Single(value) => Ok(value),
|
||||
TemplateValue::Multi(_) => create_tuliprox_error_result!(TuliproxErrorKind::Info, "Multi value templates are not supported for pattern!"),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![allow(clippy::empty_docs)]
|
||||
|
||||
use crate::foundation::filter::ValueAccessor;
|
||||
use crate::foundation::filter::{PatternTemplate, TemplateValue, ValueAccessor};
|
||||
use crate::foundation::mapper::EvalResult::{AnyValue, Failure, Named, Number, Undefined, Value};
|
||||
use crate::tuliprox_error::{create_tuliprox_error_result, info_err, TuliproxError, TuliproxErrorKind};
|
||||
use crate::utils::Capitalize;
|
||||
@@ -35,7 +35,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" }
|
||||
function_name = { "concat" | "uppercase" | "lowercase" | "capitalize" | "trim" | "print" | "number" | "first" | "template" }
|
||||
function_call = { function_name ~ "(" ~ (expression ~ ("," ~ expression)*)? ~ ")" }
|
||||
any_match = { "_" }
|
||||
match_case_key = { any_match | identifier }
|
||||
@@ -104,6 +104,7 @@ enum BuiltInFunction {
|
||||
Print,
|
||||
ToNumber,
|
||||
First,
|
||||
Template,
|
||||
}
|
||||
|
||||
impl FromStr for BuiltInFunction {
|
||||
@@ -119,6 +120,7 @@ impl FromStr for BuiltInFunction {
|
||||
"print" => Ok(Self::Print),
|
||||
"number" => Ok(Self::ToNumber),
|
||||
"first" => Ok(Self::First),
|
||||
"template" => Ok(Self::Template),
|
||||
_ => create_tuliprox_error_result!(TuliproxErrorKind::Info, "Unknown function {}", s),
|
||||
}
|
||||
}
|
||||
@@ -165,8 +167,8 @@ pub struct MapperScript {
|
||||
}
|
||||
|
||||
impl MapperScript {
|
||||
pub fn eval(&self, setter: &mut ValueAccessor) {
|
||||
let ctx = &mut MapperContext::new(&self.expressions);
|
||||
pub fn eval(&self, setter: &mut ValueAccessor, templates: Option<&Vec<PatternTemplate>>) {
|
||||
let ctx = &mut MapperContext::new(&self.expressions, templates);
|
||||
self.eval_with_context(ctx, setter);
|
||||
}
|
||||
|
||||
@@ -201,8 +203,8 @@ impl Statement {
|
||||
}
|
||||
|
||||
impl MapperScript {
|
||||
fn validate(expressions: &Vec<Expression>, statements: &Vec<Statement>) -> Result<(), TuliproxError> {
|
||||
let ctx = &mut MapperContext::new(expressions);
|
||||
fn validate(expressions: &Vec<Expression>, statements: &Vec<Statement>, templates: Option<&Vec<PatternTemplate>>) -> Result<(), TuliproxError> {
|
||||
let ctx = &mut MapperContext::new(expressions, templates);
|
||||
|
||||
let mut identifiers: HashSet<String> = HashSet::new();
|
||||
for stmt in statements {
|
||||
@@ -216,7 +218,7 @@ impl MapperScript {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn parse(input: &str) -> Result<Self, TuliproxError> {
|
||||
pub fn parse(input: &str, templates: Option<&Vec<PatternTemplate>>) -> Result<Self, TuliproxError> {
|
||||
let mut parsed = MapperParser::parse(Rule::main, input).map_err(|e| info_err!(e.to_string()))?;
|
||||
let program_pair = parsed.next().unwrap();
|
||||
let mut statements = Vec::new();
|
||||
@@ -227,7 +229,7 @@ impl MapperScript {
|
||||
}
|
||||
}
|
||||
|
||||
MapperScript::validate(&expressions, &statements)?;
|
||||
MapperScript::validate(&expressions, &statements, templates)?;
|
||||
Ok(Self { expressions, statements })
|
||||
}
|
||||
fn parse_statement(pair: Pair<Rule>, expressions: &mut Vec<Expression>) -> Result<Option<Statement>, TuliproxError> {
|
||||
@@ -536,13 +538,37 @@ impl MapperScript {
|
||||
pub struct MapperContext<'a> {
|
||||
expressions: &'a Vec<Expression>,
|
||||
variables: HashMap<String, EvalResult>,
|
||||
templates: Option<HashMap<String, &'a PatternTemplate>>,
|
||||
}
|
||||
|
||||
impl<'a> MapperContext<'a> {
|
||||
fn new(expressions: &'a Vec<Expression>) -> Self {
|
||||
fn new(expressions: &'a Vec<Expression>, templates: Option<&'a Vec<PatternTemplate>>) -> Self {
|
||||
Self {
|
||||
expressions,
|
||||
variables: HashMap::new(),
|
||||
templates: templates.and_then(|vec_templates| {
|
||||
if vec_templates.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let mut hash_map = HashMap::new();
|
||||
for template in vec_templates {
|
||||
hash_map.insert(template.name.to_string(), template);
|
||||
}
|
||||
Some(hash_map)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_template(&self, name: &str) -> Option<&str> {
|
||||
match self.templates.as_ref() {
|
||||
None => None,
|
||||
Some(templates) => templates.get(name).and_then(|&template| {
|
||||
match &template.value {
|
||||
TemplateValue::Single(v) => Some(v.as_str()),
|
||||
TemplateValue::Multi(_) => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -601,6 +627,7 @@ impl<'a> MapperContext<'a> {
|
||||
}
|
||||
match name {
|
||||
BuiltInFunction::ToNumber
|
||||
| BuiltInFunction::Template
|
||||
| BuiltInFunction::First => {
|
||||
if args.len() > 1 {
|
||||
return create_tuliprox_error_result!(TuliproxErrorKind::Info, "Function accepts only one argument {:?}, {} given", name, args.len());
|
||||
@@ -995,6 +1022,29 @@ impl Expression {
|
||||
None => Undefined,
|
||||
}
|
||||
}
|
||||
BuiltInFunction::Template => {
|
||||
let evaluated_arg = &evaluated_args[0];
|
||||
let value = match evaluated_arg {
|
||||
Value(value) => {
|
||||
Some(value)
|
||||
}
|
||||
Named(values) => {
|
||||
match values.first() {
|
||||
None => None,
|
||||
Some((_key, val)) => Some(val),
|
||||
}
|
||||
}
|
||||
_ => None
|
||||
};
|
||||
if let Some(val) = value {
|
||||
match ctx.get_template(val) {
|
||||
Some(v) => Value(v.to_string()),
|
||||
None => Undefined,
|
||||
}
|
||||
} else {
|
||||
Undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,7 @@ pub struct ConfigRename {
|
||||
|
||||
impl ConfigRename {
|
||||
pub fn prepare(&mut self, templates: Option<&Vec<PatternTemplate>>) -> Result<(), TuliproxError> {
|
||||
if let Some(templ) = templates {
|
||||
self.pattern = apply_templates_to_pattern_single(&self.pattern, templ)?;
|
||||
}
|
||||
self.pattern = apply_templates_to_pattern_single(&self.pattern, templates)?;
|
||||
match regex::Regex::new(&self.pattern) {
|
||||
Ok(pattern) => {
|
||||
self.re = Some(pattern);
|
||||
|
||||
@@ -41,7 +41,7 @@ impl ConfigSortGroup {
|
||||
(Some(seqs), Some(tmpls)) => {
|
||||
let mut result = Vec::new();
|
||||
for s in seqs {
|
||||
match apply_templates_to_pattern(s, tmpls, true)? {
|
||||
match apply_templates_to_pattern(s, Some(tmpls), true)? {
|
||||
TemplateValue::Single(val) => result.push(val),
|
||||
TemplateValue::Multi(vals) => result.extend(vals),
|
||||
}
|
||||
@@ -75,9 +75,7 @@ pub struct ConfigSortChannel {
|
||||
|
||||
impl ConfigSortChannel {
|
||||
pub fn prepare(&mut self, templates: Option<&Vec<PatternTemplate>>) -> Result<(), TuliproxError> {
|
||||
if let Some(templ) = templates {
|
||||
self.group_pattern = apply_templates_to_pattern_single(&self.group_pattern, templ)?;
|
||||
}
|
||||
self.group_pattern = apply_templates_to_pattern_single(&self.group_pattern, templates)?;
|
||||
// Compile group_pattern
|
||||
self.t_re_group_pattern = Some(
|
||||
Regex::new(&self.group_pattern).map_err(|err| {
|
||||
@@ -90,7 +88,7 @@ impl ConfigSortChannel {
|
||||
(Some(seqs), Some(tmpls)) => {
|
||||
let mut result = Vec::new();
|
||||
for s in seqs {
|
||||
match apply_templates_to_pattern(s, tmpls, true)? {
|
||||
match apply_templates_to_pattern(s, Some(tmpls), true)? {
|
||||
TemplateValue::Single(val) => result.push(val),
|
||||
TemplateValue::Multi(vals) => result.extend(vals),
|
||||
}
|
||||
|
||||
+10
-6
@@ -129,8 +129,8 @@ impl MapperOperation {
|
||||
return Err(info_err!(format!("Invalid mapper attribute field {field}")));
|
||||
}
|
||||
|
||||
if let Some(template_list) = templates {
|
||||
*value = apply_templates_to_pattern_single(value, template_list)?;
|
||||
if templates.is_some() {
|
||||
*value = apply_templates_to_pattern_single(value, templates)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,12 +158,13 @@ impl Mapper {
|
||||
Ok(filter) => self.t_filter = Some(filter),
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
let script = match templates {
|
||||
None => self.script.to_string(),
|
||||
Some(tmpls) => apply_templates_to_pattern_single(&self.script, tmpls)?,
|
||||
let script = if templates.is_some() {
|
||||
apply_templates_to_pattern_single(&self.script, templates)?
|
||||
} else {
|
||||
self.script.to_string()
|
||||
};
|
||||
trace!("Mapper script: {script}");
|
||||
self.t_script = Some(MapperScript::parse(&script)?);
|
||||
self.t_script = Some(MapperScript::parse(&script, templates)?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -178,10 +179,13 @@ pub struct Mapping {
|
||||
pub counter: Option<Vec<MappingCounterDefinition>>,
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub t_counter: Option<Vec<MappingCounter>>,
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub(crate) templates: Option<Vec<PatternTemplate>>
|
||||
}
|
||||
|
||||
impl Mapping {
|
||||
pub fn prepare(&mut self, templates: Option<&Vec<PatternTemplate>>) -> Result<(), TuliproxError> {
|
||||
self.templates = templates.map(|t| t.iter().map(PatternTemplate::clone).collect::<Vec<_>>());
|
||||
if let Some(mapper_list) = &mut self.mapper {
|
||||
for mapper in mapper_list {
|
||||
mapper.prepare(templates)?;
|
||||
|
||||
@@ -126,13 +126,14 @@ fn map_channel(mut channel: PlaylistItem, mapping: &Mapping) -> PlaylistItem {
|
||||
let channel_name = if mapping.match_as_ascii { deunicode(&header.name) } else { header.name.to_string() };
|
||||
if mapping.match_as_ascii && log_enabled!(Level::Trace) { trace!("Decoded {} for matching to {}", &header.name, &channel_name); }
|
||||
let ref_chan = &mut channel;
|
||||
let templates = mapping.templates.as_ref();
|
||||
for m in mapper {
|
||||
if let Some(script) = m.t_script.as_ref() {
|
||||
let provider = ValueProvider { pli: &ref_chan.clone() };
|
||||
let mut accessor = ValueAccessor { pli: ref_chan };
|
||||
if let Some(filter) = &m.t_filter {
|
||||
if filter.filter(&provider) {
|
||||
script.eval(&mut accessor);
|
||||
script.eval(&mut accessor, templates);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user