Skip to content

Commit f1c4c25

Browse files
committed
Merge branch 'develop' of https://github.com/ackrep-org/pyerk-core into develop
2 parents 39b8a47 + 8eefb6a commit f1c4c25

5 files changed

Lines changed: 150 additions & 11 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ tmp*
1111
dist
1212
build
1313
.aider*
14-
.env_dev
14+
.env*

src/pyirk/irkloader.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@
77
import pathlib
88
import functools
99
import addict
10+
from types import ModuleType
1011

1112

12-
ModuleType = type(sys)
13-
1413

1514
def preserve_cwd(function):
1615
"""

src/pyirk/refactor_tools.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from ipydex import IPS
2+
import re
3+
4+
def change_entity_label(key, new_label, fpath):
5+
with open(fpath, "r", encoding="utf-8") as fp:
6+
src_lines = fp.readlines()
7+
8+
# TODO: this might be more generic
9+
regex1 = re.compile(f"^{key}[ ]?=[ ]?(p|pyirk).create_(item|relation)")
10+
11+
matching_lines = [(i, line) for i, line in enumerate(src_lines) if regex1.match(line)]
12+
13+
assert len(matching_lines) == 1, "Unexpected number of matching lines"
14+
15+
start_idx = matching_lines[0][0]
16+
end_idx = start_idx + 5
17+
18+
# TODO: make this more robust
19+
assert len(src_lines) >= end_idx
20+
21+
# TODO: This is not robust if label contains quotes or spans multiple lines
22+
regex2 = re.compile(r"""^\s*R1(?:__\w+)?\s*=\s*["|'](.*)["|']""")
23+
24+
for i, line in enumerate(src_lines[start_idx:end_idx]):
25+
if match := regex2.match(line):
26+
break
27+
else:
28+
# break was not called
29+
msg = f"Could not find matching R1 pattern in lines {start_idx} to {end_idx} of {fpath}"
30+
raise ValueError(msg)
31+
32+
original_label = match.group(1)
33+
defining_line = src_lines[start_idx + i]
34+
nbr_of_occurrences = defining_line.count(original_label)
35+
err_msg = f"original label unexpectedly occurs {nbr_of_occurrences} times in {fpath}:{start_idx + i}"
36+
assert nbr_of_occurrences == 1, err_msg
37+
new_defining_line = defining_line.replace(original_label, new_label)
38+
39+
src_lines[start_idx + i] = new_defining_line
40+
41+
new_src = "".join(src_lines)
42+
43+
replacements = [
44+
(f'{key}["{original_label}"]', f'{key}["{new_label}"]'),
45+
(f"{key}['{original_label}']", f"{key}['{new_label}']"),
46+
# TODO: add underscore_version
47+
]
48+
49+
for rplm in replacements:
50+
new_src = new_src.replace(*rplm)
51+
52+
with open(fpath, "w", encoding="utf-8") as fp:
53+
fp.write(new_src)

src/pyirk/script.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,18 @@
66
import argparse
77
from pathlib import Path
88
import re
9+
import types
910
from typing import Tuple
1011
import ast
1112
import inspect
1213
from textwrap import dedent
14+
from addict import Addict as Container
1315

1416
try:
1517
# this will be part of standard library for python >= 3.11
1618
import tomllib
1719
except ModuleNotFoundError:
18-
import tomli as tomllib
20+
import tomli as tomllib # type: ignore
1921

2022
import platformdirs
2123

@@ -34,8 +36,8 @@
3436

3537
def create_parser():
3638
"""
37-
Returns the parser object which is then evaluated in main(). This is necessary for sphinx to automatically
38-
generate the cli docs.
39+
Returns the parser object which is then evaluated in main(). This is necessary for sphinx to
40+
automatically generate the cli docs.
3941
"""
4042

4143
parser = argparse.ArgumentParser(
@@ -173,6 +175,15 @@ def create_parser():
173175
action="store_true",
174176
)
175177

178+
parser.add_argument(
179+
"--refactor-entity-label",
180+
help="change main label of entity in source file of loaded module. "
181+
"Recommended: Apply ony to clean git repo.",
182+
nargs=2,
183+
metavar=("key", "new label")
184+
)
185+
186+
176187
return parser
177188

178189

@@ -183,6 +194,13 @@ def main():
183194
debug()
184195
exit()
185196

197+
if args.refactor_entity_label:
198+
from . import refactor_tools as rt
199+
assert args.inputfile is not None
200+
assert len(args.refactor_entity_label) == 2
201+
rt.change_entity_label(*args.refactor_entity_label, args.inputfile)
202+
exit()
203+
186204
if args.version:
187205
print(release.__version__)
188206
exit()
@@ -235,15 +253,15 @@ def main():
235253
visualization.visualize_entity(uri, write_tmp_files=True)
236254
elif args.start_django:
237255
try:
238-
import pyirkdjango.core
256+
import pyirkdjango.core # type: ignore
239257
except ImportError:
240258
print(aux.bred("Error:"), "the module pyirkdjango seems not to be installed.")
241259
# exit(10)
242260
raise
243261
pyirkdjango.core.start_django()
244262
elif args.start_django_shell:
245263
try:
246-
import pyirkdjango.core
264+
import pyirkdjango.core # type: ignore
247265
except ImportError:
248266
print(aux.bred("Error:"), "the module pyirkdjango seems not to be installed.")
249267
# exit(10)
@@ -259,7 +277,7 @@ def main():
259277
print("nothing to do, see option `--help` for more info")
260278

261279

262-
def process_package(pkg_path: str) -> Tuple[irkloader.ModuleType, str]:
280+
def process_package(pkg_path: str) -> Tuple[types.ModuleType, str]:
263281
if os.path.isdir(pkg_path):
264282
pkg_path = os.path.join(pkg_path, "irkpackage.toml")
265283

@@ -273,7 +291,7 @@ def process_package(pkg_path: str) -> Tuple[irkloader.ModuleType, str]:
273291
return mod, main_module_prefix
274292

275293

276-
def process_mod(path: str, prefix: str, relative_to_workdir: bool = False) -> irkloader.ModuleType:
294+
def process_mod(path: str, prefix: str, relative_to_workdir: bool = False) -> types.ModuleType:
277295
if not relative_to_workdir:
278296
msg = "using mod paths which are not relative to workdir is deprecated since pyirk version 0.6.0"
279297
raise DeprecationWarning(msg)
@@ -522,7 +540,7 @@ def process_template(template_path):
522540
return rendered_template
523541

524542

525-
def path_to_ast_container(mod_path: str) -> core.aux.Container:
543+
def path_to_ast_container(mod_path: str) -> Container:
526544

527545
with open(mod_path) as fp:
528546
lines = fp.readlines()

tests/test_refactor_tools.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import os
2+
import unittest
3+
import tempfile
4+
import shutil
5+
6+
import pyirk.refactor_tools as rt
7+
8+
9+
10+
from .settings import (
11+
TEST_BASE_URI,
12+
TEST_DATA_DIR1,
13+
HousekeeperMixin,
14+
TEST_DATA_DIR_OCSE,
15+
TEST_DATA_PATH3 as ocse_subset_agents_path,
16+
)
17+
18+
19+
# noinspection PyPep8Naming
20+
class Test_01_Script(HousekeeperMixin, unittest.TestCase):
21+
def test_rt_a01__change_entity_label(self):
22+
23+
tmp_mod_fpath = make_temp_copy_of_file(ocse_subset_agents_path)
24+
25+
# change I9942["Stanford University"] to I9942["Renamed Stanford University"]
26+
rt.change_entity_label(key="I9942", new_label="Renamed Stanford University", fpath=tmp_mod_fpath)
27+
28+
manually_checking_difference = False
29+
if manually_checking_difference:
30+
tmp_mod_fpath0 = make_temp_copy_of_file(ocse_subset_agents_path)
31+
os.system(f"kdiff3 {tmp_mod_fpath0} {tmp_mod_fpath}")
32+
33+
34+
def test_rt_a02__change_entity_label_from_cli(self):
35+
36+
tmp_mod_fpath = make_temp_copy_of_file(ocse_subset_agents_path)
37+
38+
cmd = f'pyirk --refactor-entity-label I9942 "Renamed Stanford University" {tmp_mod_fpath}'
39+
os.system(cmd)
40+
41+
42+
manually_checking_difference = False
43+
if manually_checking_difference:
44+
tmp_mod_fpath0 = make_temp_copy_of_file(ocse_subset_agents_path)
45+
os.system(f"kdiff3 {tmp_mod_fpath0} {tmp_mod_fpath}")
46+
#
47+
# Auxiliary functions:
48+
#
49+
50+
def make_temp_copy_of_file(fpath):
51+
"""
52+
Creates a temporary file with the content of the file specified by `fpath`
53+
"""
54+
# Create a temporary file and copy the content from the source file
55+
temp_fd, temp_path = tempfile.mkstemp(suffix=".py")
56+
try:
57+
# Close the file descriptor since shutil.copy2 will handle the file operations
58+
import os
59+
os.close(temp_fd)
60+
# Copy the file content and metadata
61+
shutil.copy2(fpath, temp_path)
62+
return temp_path
63+
except Exception:
64+
# Clean up the temporary file if something goes wrong
65+
try:
66+
os.unlink(temp_path)
67+
except:
68+
pass
69+
raise

0 commit comments

Comments
 (0)