@@ -1643,16 +1643,22 @@ def apply_patch(patch_file, dest, fn=None, copy=False, level=None, use_git_am=Fa
1643
1643
return True
1644
1644
1645
1645
1646
- def apply_regex_substitutions (paths , regex_subs , backup = '.orig.eb' , on_missing_match = None ):
1646
+ def apply_regex_substitutions (paths , regex_subs , backup = '.orig.eb' ,
1647
+ on_missing_match = None , match_all = False , single_line = True ):
1647
1648
"""
1648
1649
Apply specified list of regex substitutions.
1649
1650
1650
1651
:param paths: list of paths to files to patch (or just a single filepath)
1651
- :param regex_subs: list of substitutions to apply, specified as (<regexp pattern>, <replacement string>)
1652
+ :param regex_subs: list of substitutions to apply,
1653
+ specified as (<regexp pattern or regex instance>, <replacement string>)
1652
1654
:param backup: create backup of original file with specified suffix (no backup if value evaluates to False)
1653
1655
:param on_missing_match: Define what to do when no match was found in the file.
1654
1656
Can be 'error' to raise an error, 'warn' to print a warning or 'ignore' to do nothing
1655
1657
Defaults to the value of --strict
1658
+ :param match_all: Expect to match all patterns in all files
1659
+ instead of at least one per file for error/warning reporting
1660
+ :param single_line: Replace first match of each pattern for each line in the order of the patterns.
1661
+ If False the patterns are applied in order to the full text and may match line breaks.
1656
1662
"""
1657
1663
if on_missing_match is None :
1658
1664
on_missing_match = build_option ('strict' )
@@ -1664,18 +1670,22 @@ def apply_regex_substitutions(paths, regex_subs, backup='.orig.eb', on_missing_m
1664
1670
if isinstance (paths , string_type ):
1665
1671
paths = [paths ]
1666
1672
1673
+ flags = 0 if single_line else re .M
1674
+ compiled_regex_subs = [(re .compile (regex , flags ) if isinstance (regex , str ) else regex , subtxt )
1675
+ for (regex , subtxt ) in regex_subs ]
1676
+
1667
1677
# only report when in 'dry run' mode
1668
1678
if build_option ('extended_dry_run' ):
1669
1679
paths_str = ', ' .join (paths )
1670
1680
dry_run_msg ("applying regex substitutions to file(s): %s" % paths_str , silent = build_option ('silent' ))
1671
- for regex , subtxt in regex_subs :
1672
- dry_run_msg (" * regex pattern '%s', replacement string '%s'" % (regex , subtxt ))
1681
+ for regex , subtxt in compiled_regex_subs :
1682
+ dry_run_msg (" * regex pattern '%s', replacement string '%s'" % (regex . pattern , subtxt ))
1673
1683
1674
1684
else :
1675
- _log .info ("Applying following regex substitutions to %s: %s" , paths , regex_subs )
1676
-
1677
- compiled_regex_subs = [(re .compile (regex ), subtxt ) for (regex , subtxt ) in regex_subs ]
1685
+ _log .info ("Applying following regex substitutions to %s: %s" ,
1686
+ paths , [(regex .pattern , subtxt ) for regex , subtxt in compiled_regex_subs ])
1678
1687
1688
+ replacement_failed_msgs = []
1679
1689
for path in paths :
1680
1690
try :
1681
1691
# make sure that file can be opened in text mode;
@@ -1695,32 +1705,49 @@ def apply_regex_substitutions(paths, regex_subs, backup='.orig.eb', on_missing_m
1695
1705
if backup :
1696
1706
copy_file (path , path + backup )
1697
1707
replacement_msgs = []
1708
+ replaced = [False ] * len (compiled_regex_subs )
1698
1709
with open_file (path , 'w' ) as out_file :
1699
- lines = txt_utf8 .split ('\n ' )
1700
- del txt_utf8
1701
- for line_id , line in enumerate (lines ):
1702
- for regex , subtxt in compiled_regex_subs :
1703
- match = regex .search (line )
1704
- if match :
1710
+ if single_line :
1711
+ lines = txt_utf8 .split ('\n ' )
1712
+ del txt_utf8
1713
+ for line_id , line in enumerate (lines ):
1714
+ for i , (regex , subtxt ) in enumerate (compiled_regex_subs ):
1715
+ match = regex .search (line )
1716
+ if match :
1717
+ origtxt = match .group (0 )
1718
+ replacement_msgs .append ("Replaced in line %d: '%s' -> '%s'" %
1719
+ (line_id + 1 , origtxt , subtxt ))
1720
+ replaced [i ] = True
1721
+ line = regex .sub (subtxt , line )
1722
+ lines [line_id ] = line
1723
+ out_file .write ('\n ' .join (lines ))
1724
+ else :
1725
+ for i , (regex , subtxt ) in enumerate (compiled_regex_subs ):
1726
+ def do_replace (match ):
1705
1727
origtxt = match .group (0 )
1706
- replacement_msgs .append ("Replaced in line %d: '%s' -> '%s'" %
1707
- (line_id + 1 , origtxt , subtxt ))
1708
- line = regex .sub (subtxt , line )
1709
- lines [line_id ] = line
1710
- out_file .write ('\n ' .join (lines ))
1728
+ # pylint: disable=cell-var-from-loop
1729
+ cur_subtxt = match .expand (subtxt )
1730
+ # pylint: disable=cell-var-from-loop
1731
+ replacement_msgs .append ("Replaced: '%s' -> '%s'" % (origtxt , cur_subtxt ))
1732
+ return cur_subtxt
1733
+ txt_utf8 , replaced [i ] = regex .subn (do_replace , txt_utf8 )
1734
+ out_file .write (txt_utf8 )
1711
1735
if replacement_msgs :
1712
1736
_log .info ('Applied the following substitutions to %s:\n %s' , path , '\n ' .join (replacement_msgs ))
1713
- else :
1714
- msg = 'Nothing found to replace in %s' % path
1715
- if on_missing_match == ERROR :
1716
- raise EasyBuildError (msg )
1717
- elif on_missing_match == WARN :
1718
- _log .warning (msg )
1719
- else :
1720
- _log .info (msg )
1721
-
1737
+ if (match_all and not all (replaced )) or (not match_all and not any (replaced )):
1738
+ errors = ["Nothing found to replace '%s'" % regex .pattern
1739
+ for cur_replaced , (regex , _ ) in zip (replaced , compiled_regex_subs ) if not cur_replaced ]
1740
+ replacement_failed_msgs .append (', ' .join (errors ) + ' in ' + path )
1722
1741
except (IOError , OSError ) as err :
1723
1742
raise EasyBuildError ("Failed to patch %s: %s" , path , err )
1743
+ if replacement_failed_msgs :
1744
+ msg = '\n ' .join (replacement_failed_msgs )
1745
+ if on_missing_match == ERROR :
1746
+ raise EasyBuildError (msg )
1747
+ elif on_missing_match == WARN :
1748
+ _log .warning (msg )
1749
+ else :
1750
+ _log .info (msg )
1724
1751
1725
1752
1726
1753
def modify_env (old , new ):
0 commit comments