Skip to content

Commit

Permalink
added method to search content string of a edm file and replace all i…
Browse files Browse the repository at this point in the history
…nstances of clac and local pv's. also added a unit test for the method
  • Loading branch information
YektaY committed Jan 17, 2025
1 parent 66680fc commit 27156f6
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 0 deletions.
63 changes: 63 additions & 0 deletions src/edm/parser_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,3 +346,66 @@ def loc_conversion(edm_string: str) -> str:
pydm_string = f"loc://{name}?type={pydm_type}&init={value}"

return pydm_string


def replace_calc_and_loc_in_edm_content(
edm_content: str, filepath: str
) -> Tuple[str, Dict[str, Dict[str, str]], Dict[str, Dict[str, str]]]:
"""
Replace both CALC\\...(...) and LOC\\...=... references in the EDM file content
with PyDM equivalents. The first time each unique reference appears, the
replacement is the full PyDM string; subsequent appearances use the short form.
Parameters
----------
edm_content : str
The full text of the EDM file, as a single string.
filepath : str
path of the given edm file
Returns
-------
new_content : str
The EDM content after all CALC and LOC references have been replaced.
encountered_calcs : Dict[str, Dict[str, str]]
A dictionary of all encountered CALC references, mapping the original
EDM reference to {"full": ..., "short": ...}.
encountered_locs : Dict[str, Dict[str, str]]
A dictionary of all encountered LOC references, similarly mapping each
unique original LOC reference to "full" and "short" addresses.
"""
calc_list_path = search_calc_list(filepath)
calc_dict = parse_calc_list(calc_list_path)

encountered_calcs: Dict[str, Dict[str, str]] = {}
encountered_locs: Dict[str, Dict[str, str]] = {}

calc_pattern = re.compile(r"CALC\\[^(\s]+\([^)]*\)")

def replace_calc_match(match: re.Match) -> str:
edm_pv = match.group(0)
if edm_pv not in encountered_calcs:
full_url = translate_calc_pv_to_pydm(edm_pv, calc_dict=calc_dict)
short_url = full_url.split("?", 1)[0]
encountered_calcs[edm_pv] = {"full": full_url, "short": short_url}
return full_url
else:
return encountered_calcs[edm_pv]["short"]

new_content = calc_pattern.sub(replace_calc_match, edm_content)

loc_pattern = re.compile(r'LOC\\[^=]+=[dies]:[^"]*')

def replace_loc_match(match: re.Match) -> str:
edm_pv = match.group(0)
if edm_pv not in encountered_locs:
full_url = loc_conversion(edm_pv)
short_url = full_url.split("?", 1)[0]
encountered_locs[edm_pv] = {"full": full_url, "short": short_url}
return full_url
else:
return encountered_locs[edm_pv]["short"]

new_content = loc_pattern.sub(replace_loc_match, new_content)

return new_content, encountered_calcs, encountered_locs
100 changes: 100 additions & 0 deletions tests/test_parser_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import pytest
import tempfile
import textwrap
from unittest.mock import patch

from src.edm.parser_helpers import (
search_calc_list,
Expand All @@ -10,6 +11,7 @@
apply_rewrite_rule,
translate_calc_pv_to_pydm,
loc_conversion,
replace_calc_and_loc_in_edm_content,
)


Expand Down Expand Up @@ -166,3 +168,101 @@ def test_loc_conversion():
edm_string_missing_colon = "LOC\\noColon=i"
with pytest.raises(ValueError):
loc_conversion(edm_string_missing_colon)


@pytest.mark.parametrize(
"edm_content",
[
r"""
object activeXTextClass {
controlPv "CALC\sum(pv1, pv2)"
}
object activeXTextClass {
controlPv "CALC\sum(pv1, pv2)"
}
object activeXTextClass {
controlPv "CALC\{A-B}(anotherPv, 10.5)"
}
object activeXTextClass {
controlPv "LOC\myLocal=d:3.14"
}
object activeXTextClass {
controlPv "LOC\myLocal=d:3.14"
}
object activeXTextClass {
controlPv "LOC\myLocalInt=i:42"
}
"""
],
)
@patch("src.edm.parser_helpers.loc_conversion")
@patch("src.edm.parser_helpers.translate_calc_pv_to_pydm")
@patch("src.edm.parser_helpers.parse_calc_list")
@patch("src.edm.parser_helpers.search_calc_list")
def test_replace_calc_and_loc_in_edm_content(
mock_search_calc_list, mock_parse_calc_list, mock_translate_calc_pv_to_pydm, mock_loc_conversion, edm_content
):
"""
Tests that replace_calc_and_loc_in_edm_content correctly:
- Finds and replaces CALC\ and LOC\ references.
- Uses full PyDM strings on first occurrence, short references subsequently.
- Returns dictionaries for encountered references.
"""
mock_search_calc_list.return_value = "/fake/path/calc.list"
mock_parse_calc_list.return_value = {
"sum": (None, "A+B"),
"avg": (None, "(A+B)/2"),
}

def mock_calc_translate(edm_pv, calc_dict=None):
if "sum" in edm_pv:
return "calc://sum?A=channel://pv1&B=channel://pv2&expr=A+B"
elif "{A-B}" in edm_pv:
return "calc://inline_expr?A=channel://anotherPv&B=channel://10.5&expr=A-B"
return "calc://unknown_calc"

mock_translate_calc_pv_to_pydm.side_effect = mock_calc_translate

def mock_loc_conv(edm_pv):
if "LOC\\myLocal=d:3.14" in edm_pv:
return "loc://myLocal?type=float&init=3.14"
elif "LOC\\myLocalInt=i:42" in edm_pv:
return "loc://myLocalInt?type=int&init=42"
return "loc://unknown_local"

mock_loc_conversion.side_effect = mock_loc_conv

new_content, encountered_calcs, encountered_locs = replace_calc_and_loc_in_edm_content(
edm_content, filepath="/some/fake/edm_file.edl"
)

mock_search_calc_list.assert_called_once_with("/some/fake/edm_file.edl")
mock_parse_calc_list.assert_called_once_with("/fake/path/calc.list")

assert "calc://sum?A=channel://pv1&B=channel://pv2&expr=A+B" in new_content
assert "calc://sum" in new_content
assert "calc://inline_expr?A=channel://anotherPv&B=channel://10.5&expr=A-B" in new_content

assert "loc://myLocal?type=float&init=3.14" in new_content
assert "loc://myLocal" in new_content
assert "loc://myLocalInt?type=int&init=42" in new_content

assert r"CALC\sum(pv1, pv2)" in encountered_calcs
sum_entry = encountered_calcs[r"CALC\sum(pv1, pv2)"]
assert sum_entry["full"] == "calc://sum?A=channel://pv1&B=channel://pv2&expr=A+B"
assert sum_entry["short"] == "calc://sum"

assert r"CALC\{A-B}(anotherPv, 10.5)" in encountered_calcs
inline_entry = encountered_calcs[r"CALC\{A-B}(anotherPv, 10.5)"]
assert inline_entry["full"] == "calc://inline_expr?A=channel://anotherPv&B=channel://10.5&expr=A-B"
assert inline_entry["short"] == "calc://inline_expr"

assert r"LOC\myLocal=d:3.14" in encountered_locs
myLocal_entry = encountered_locs[r"LOC\myLocal=d:3.14"]
assert myLocal_entry["full"] == "loc://myLocal?type=float&init=3.14"
assert myLocal_entry["short"] == "loc://myLocal"

assert r"LOC\myLocalInt=i:42" in encountered_locs
myLocalInt_entry = encountered_locs[r"LOC\myLocalInt=i:42"]
assert myLocalInt_entry["full"] == "loc://myLocalInt?type=int&init=42"
assert myLocalInt_entry["short"] == "loc://myLocalInt"

0 comments on commit 27156f6

Please sign in to comment.