Skip to content

Commit

Permalink
[mqtt.homeassistant] Implement regex_findall and regex_findall_index …
Browse files Browse the repository at this point in the history
…filters (openhab#17895)

Signed-off-by: Cody Cutrer <[email protected]>
  • Loading branch information
ccutrer authored Dec 18, 2024
1 parent a94e4a1 commit f45f29f
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ public UndefinedException(JinjavaInterpreter interpreter) {
private final Logger logger = LoggerFactory.getLogger(HomeAssistantChannelTransformation.class);

private final Jinjava jinjava;
private final AbstractComponent component;
private final AbstractComponent<?> component;
private final String template;
private final ObjectMapper objectMapper = new ObjectMapper();

public HomeAssistantChannelTransformation(Jinjava jinjava, AbstractComponent component, String template) {
public HomeAssistantChannelTransformation(Jinjava jinjava, AbstractComponent<?> component, String template) {
super((String) null);
this.jinjava = jinjava;
this.component = component;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,24 @@

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

import com.google.re2j.Matcher;
import com.google.re2j.Pattern;
import com.google.re2j.PatternSyntaxException;
import com.hubspot.jinjava.interpret.Context;
import com.hubspot.jinjava.interpret.InterpretException;
import com.hubspot.jinjava.interpret.InvalidArgumentException;
import com.hubspot.jinjava.interpret.InvalidReason;
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
import com.hubspot.jinjava.interpret.TemplateSyntaxException;
import com.hubspot.jinjava.lib.filter.Filter;
import com.hubspot.jinjava.lib.fn.ELFunctionDefinition;
import com.hubspot.jinjava.util.ObjectTruthValue;
Expand All @@ -39,15 +48,17 @@ public static void register(Context context) {
new ELFunctionDefinition("", "iif", Functions.class, "iif", Object.class, Object[].class));
context.registerFilter(new SimpleFilter("iif", Functions.class, "iif", Object.class, Object[].class));
context.registerFilter(new IsDefinedFilter());
context.registerFilter(new RegexFindAllFilter());
context.registerFilter(new RegexFindAllIndexFilter());
}

@NonNullByDefault({})
private static class SimpleFilter implements Filter {
private final String name;
private final Method method;
private final Class klass;
private final Class<?> klass;

public SimpleFilter(String name, Class klass, String methodName, Class... args) {
public SimpleFilter(String name, Class<?> klass, String methodName, Class<?>... args) {
this.name = name;
this.klass = klass;
try {
Expand Down Expand Up @@ -110,9 +121,135 @@ public Object filter(Object var, JinjavaInterpreter interpreter, String... args)
}
}

// https://www.home-assistant.io/docs/configuration/templating/#regular-expressions
// https://github.com/home-assistant/core/blob/2024.12.2/homeassistant/helpers/template.py#L2453
@NonNullByDefault({})
private static class RegexFindAllFilter implements Filter {
@Override
public String getName() {
return "regex_findall";
}

@Override
public Object filter(Object var, JinjavaInterpreter interpreter, String... args) {
if (args.length > 2) {
throw new TemplateSyntaxException(interpreter, getName(),
"requires at most 2 arguments (regex string, ignore case)");
}

String find = null;
if (args.length >= 1) {
find = args[0];
}
String ignoreCase = null;
if (args.length == 2) {
ignoreCase = args[1];
}

Matcher m = regexFindAll(var, interpreter, find, ignoreCase);

List<Object> result = new ArrayList<>();
while (m.find()) {
result.add(resultForMatcher(m));
}

return result;
}

protected Object resultForMatcher(Matcher m) {
if (m.groupCount() == 0) {
return m.group();
} else if (m.groupCount() == 1) {
return m.group(1);
} else {
List<String> groups = new ArrayList<>(m.groupCount());
for (int i = 1; i <= m.groupCount(); ++i) {
groups.add(m.group(i));
}
return groups;
}
}

protected Matcher regexFindAll(Object var, JinjavaInterpreter interpreter, String find, String ignoreCaseStr) {
String s;
if (var == null) {
s = "None";
} else {
s = var.toString();
}

boolean ignoreCase = ObjectTruthValue.evaluate(ignoreCaseStr);
int flags = 0;
if (ignoreCase) {
flags = Pattern.CASE_INSENSITIVE;
}

Pattern p;
try {
if (find instanceof String findString) {
p = Pattern.compile(findString, flags);
} else if (find == null) {
p = Pattern.compile("", flags);
} else {
throw new InvalidArgumentException(interpreter, this, InvalidReason.REGEX, 0, find);
}

return p.matcher(s);
} catch (PatternSyntaxException e) {
throw new InvalidArgumentException(interpreter, this, InvalidReason.REGEX, 0, find);
}
}
}

// https://www.home-assistant.io/docs/configuration/templating/#regular-expressions
// https://github.com/home-assistant/core/blob/2024.12.2/homeassistant/helpers/template.py#L2448
@NonNullByDefault({})
private static class RegexFindAllIndexFilter extends RegexFindAllFilter {
@Override
public String getName() {
return "regex_findall_index";
}

@Override
public Object filter(Object var, JinjavaInterpreter interpreter, String... args) {
if (args.length > 3) {
throw new TemplateSyntaxException(interpreter, getName(),
"requires at most 3 arguments (regex string, index, ignore case)");
}

String find = null;
if (args.length >= 1) {
find = args[0];
}
int index = 0;
if (args.length >= 2) {
index = Integer.valueOf(args[1]);
if (index < 0) {
throw new InvalidArgumentException(interpreter, this, InvalidReason.POSITIVE_NUMBER, 1, args[1]);
}
}

String ignoreCase = null;
if (args.length == 3) {
ignoreCase = args[2];
}

Matcher m = regexFindAll(var, interpreter, find, ignoreCase);
int i = 0;
while (i <= index) {
if (!m.find()) {
break;
}
i += 1;
}

return resultForMatcher(m);
}
}

private static class Functions {
// https://www.home-assistant.io/docs/configuration/templating/#immediate-if-iif
public static Object iif(Object value, Object... results) {
public static @Nullable Object iif(@Nullable Object value, @Nullable Object... results) {
if (results.length > 3) {
throw new IllegalArgumentException("Parameters for function 'iff' do not match");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,24 @@ public void testIsDefined() {
assertThat(transform("{{ 'hi' | is_defined }}", "{}"), is("hi"));
}

@Test
public void testRegexFindall() {
assertThat(transform("{{ 'Flight from JFK to LHR' | regex_findall('([A-Z]{3})') }}", ""), is("[JFK, LHR]"));
assertThat(transform(
"{{ 'button_up_press' | regex_findall('^(?P<button>(?:button_)?[a-z0-9]+)_(?P<action>(?:press|hold)(?:_release)?)$') }}",
""), is("[[button_up, press]]"));
}

@Test
public void testRegexFindallIndex() {
assertThat(transform("{{ 'Flight from JFK to LHR' | regex_findall_index('([A-Z]{3})', 0) }}", ""), is("JFK"));
assertThat(transform("{{ 'Flight from JFK to LHR' | regex_findall_index('([A-Z]{3})', 1) }}", ""), is("LHR"));
assertThat(transform("{{ ['JFK', 'LHR'] | regex_findall_index('([A-Z]{3})', 1) }}", ""), is("LHR"));
assertThat(transform(
"{{ 'button_up_press' | regex_findall_index('^(?P<button>(?:button_)?[a-z0-9]+)_(?P<action>(?:press|hold)(?:_release)?)$') }}",
""), is("[button_up, press]"));
}

protected @Nullable String transform(String template, String value) {
return transformation.apply(template, value).orElse(null);
}
Expand Down

0 comments on commit f45f29f

Please sign in to comment.