Skip to content

Commit 44470b6

Browse files
authored
Merge pull request #3440 from Flamefire/apply_regex_substitutions
enhance apply_regex_substitutions to allow specifying action to take in case there are no matches
2 parents 4a752a9 + cc93c06 commit 44470b6

File tree

3 files changed

+63
-10
lines changed

3 files changed

+63
-10
lines changed

easybuild/tools/filetools.py

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@
5858
from easybuild.tools import run
5959
# import build_log must stay, to use of EasyBuildLog
6060
from easybuild.tools.build_log import EasyBuildError, dry_run_msg, print_msg, print_warning
61-
from easybuild.tools.config import (DEFAULT_WAIT_ON_LOCK_INTERVAL, GENERIC_EASYBLOCK_PKG, build_option, install_path,
62-
IGNORE, WARN, ERROR)
61+
from easybuild.tools.config import DEFAULT_WAIT_ON_LOCK_INTERVAL, ERROR, GENERIC_EASYBLOCK_PKG, IGNORE, WARN
62+
from easybuild.tools.config import build_option, install_path
6363
from easybuild.tools.py2vs3 import HTMLParser, std_urllib, string_type
6464
from easybuild.tools.utilities import natural_keys, nub, remove_unwanted_chars
6565

@@ -1474,14 +1474,24 @@ def apply_patch(patch_file, dest, fn=None, copy=False, level=None, use_git_am=Fa
14741474
return True
14751475

14761476

1477-
def apply_regex_substitutions(paths, regex_subs, backup='.orig.eb'):
1477+
def apply_regex_substitutions(paths, regex_subs, backup='.orig.eb', on_missing_match=None):
14781478
"""
14791479
Apply specified list of regex substitutions.
14801480
14811481
:param paths: list of paths to files to patch (or just a single filepath)
14821482
:param regex_subs: list of substitutions to apply, specified as (<regexp pattern>, <replacement string>)
14831483
:param backup: create backup of original file with specified suffix (no backup if value evaluates to False)
1484+
:param on_missing_match: Define what to do when no match was found in the file.
1485+
Can be 'error' to raise an error, 'warn' to print a warning or 'ignore' to do nothing
1486+
Defaults to the value of --strict
14841487
"""
1488+
if on_missing_match is None:
1489+
on_missing_match = build_option('strict')
1490+
allowed_values = (ERROR, IGNORE, WARN)
1491+
if on_missing_match not in allowed_values:
1492+
raise EasyBuildError('Invalid value passed to on_missing_match: %s (allowed: %s)',
1493+
on_missing_match, ', '.join(allowed_values))
1494+
14851495
if isinstance(paths, string_type):
14861496
paths = [paths]
14871497

@@ -1495,9 +1505,7 @@ def apply_regex_substitutions(paths, regex_subs, backup='.orig.eb'):
14951505
else:
14961506
_log.info("Applying following regex substitutions to %s: %s", paths, regex_subs)
14971507

1498-
compiled_regex_subs = []
1499-
for regex, subtxt in regex_subs:
1500-
compiled_regex_subs.append((re.compile(regex), subtxt))
1508+
compiled_regex_subs = [(re.compile(regex), subtxt) for (regex, subtxt) in regex_subs]
15011509

15021510
for path in paths:
15031511
try:
@@ -1517,6 +1525,7 @@ def apply_regex_substitutions(paths, regex_subs, backup='.orig.eb'):
15171525

15181526
if backup:
15191527
copy_file(path, path + backup)
1528+
replacement_msgs = []
15201529
with open_file(path, 'w') as out_file:
15211530
lines = txt_utf8.split('\n')
15221531
del txt_utf8
@@ -1525,11 +1534,21 @@ def apply_regex_substitutions(paths, regex_subs, backup='.orig.eb'):
15251534
match = regex.search(line)
15261535
if match:
15271536
origtxt = match.group(0)
1528-
_log.info("Replacing line %d in %s: '%s' -> '%s'",
1529-
(line_id + 1), path, origtxt, subtxt)
1537+
replacement_msgs.append("Replaced in line %d: '%s' -> '%s'" %
1538+
(line_id + 1, origtxt, subtxt))
15301539
line = regex.sub(subtxt, line)
15311540
lines[line_id] = line
15321541
out_file.write('\n'.join(lines))
1542+
if replacement_msgs:
1543+
_log.info('Applied the following substitutions to %s:\n%s', path, '\n'.join(replacement_msgs))
1544+
else:
1545+
msg = 'Nothing found to replace in %s' % path
1546+
if on_missing_match == ERROR:
1547+
raise EasyBuildError(msg)
1548+
elif on_missing_match == WARN:
1549+
_log.warning(msg)
1550+
else:
1551+
_log.info(msg)
15331552

15341553
except (IOError, OSError) as err:
15351554
raise EasyBuildError("Failed to patch %s: %s", path, err)

test/framework/filetools.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
import time
4343
from test.framework.utilities import EnhancedTestCase, TestLoaderFiltered, init_config
4444
from unittest import TextTestRunner
45-
45+
from easybuild.tools import run
4646
import easybuild.tools.filetools as ft
4747
from easybuild.tools.build_log import EasyBuildError
4848
from easybuild.tools.config import IGNORE, ERROR
@@ -1219,6 +1219,7 @@ def test_apply_regex_substitutions(self):
12191219
(r"^(FC\s*=\s*).*$", r"\1${FC}"),
12201220
(r"^(.FLAGS)\s*=\s*-O3\s-g(.*)$", r"\1 = -O2\2"),
12211221
]
1222+
regex_subs_copy = regex_subs[:]
12221223
ft.apply_regex_substitutions(testfile, regex_subs)
12231224

12241225
expected_testtxt = '\n'.join([
@@ -1229,6 +1230,8 @@ def test_apply_regex_substitutions(self):
12291230
])
12301231
new_testtxt = ft.read_file(testfile)
12311232
self.assertEqual(new_testtxt, expected_testtxt)
1233+
# Must not have touched the list
1234+
self.assertEqual(regex_subs_copy, regex_subs)
12321235

12331236
# backup file is created by default
12341237
backup = testfile + '.orig.eb'
@@ -1261,10 +1264,30 @@ def test_apply_regex_substitutions(self):
12611264

12621265
# passing empty list of substitions is a no-op
12631266
ft.write_file(testfile, testtxt)
1264-
ft.apply_regex_substitutions(testfile, [])
1267+
ft.apply_regex_substitutions(testfile, [], on_missing_match=run.IGNORE)
12651268
new_testtxt = ft.read_file(testfile)
12661269
self.assertEqual(new_testtxt, testtxt)
12671270

1271+
# Check handling of on_missing_match
1272+
ft.write_file(testfile, testtxt)
1273+
regex_subs_no_match = [('Not there', 'Not used')]
1274+
error_pat = 'Nothing found to replace in %s' % testfile
1275+
# Error
1276+
self.assertErrorRegex(EasyBuildError, error_pat, ft.apply_regex_substitutions, testfile, regex_subs_no_match,
1277+
on_missing_match=run.ERROR)
1278+
1279+
# Warn
1280+
with self.log_to_testlogfile():
1281+
ft.apply_regex_substitutions(testfile, regex_subs_no_match, on_missing_match=run.WARN)
1282+
logtxt = ft.read_file(self.logfile)
1283+
self.assertTrue('WARNING ' + error_pat in logtxt)
1284+
1285+
# Ignore
1286+
with self.log_to_testlogfile():
1287+
ft.apply_regex_substitutions(testfile, regex_subs_no_match, on_missing_match=run.IGNORE)
1288+
logtxt = ft.read_file(self.logfile)
1289+
self.assertTrue('INFO ' + error_pat in logtxt)
1290+
12681291
# clean error on non-existing file
12691292
error_pat = "Failed to patch .*/nosuchfile.txt: .*No such file or directory"
12701293
path = os.path.join(self.test_prefix, 'nosuchfile.txt')

test/framework/utilities.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import sys
3737
import tempfile
3838
import unittest
39+
from contextlib import contextmanager
3940

4041
from easybuild.base import fancylogger
4142
from easybuild.base.testing import TestCase
@@ -205,6 +206,16 @@ def allow_deprecated_behaviour(self):
205206
del os.environ['EASYBUILD_DEPRECATED']
206207
eb_build_log.CURRENT_VERSION = self.orig_current_version
207208

209+
@contextmanager
210+
def log_to_testlogfile(self):
211+
"""Context manager class to capture log output in self.logfile for the scope used. Clears the file first"""
212+
open(self.logfile, 'w').close() # Remove all contents
213+
fancylogger.logToFile(self.logfile)
214+
try:
215+
yield self.logfile
216+
finally:
217+
fancylogger.logToFile(self.logfile, enable=False)
218+
208219
def tearDown(self):
209220
"""Clean up after running testcase."""
210221
super(EnhancedTestCase, self).tearDown()

0 commit comments

Comments
 (0)