Skip to content

Commit 657ae18

Browse files
authored
Fix re replace issue (#468)
* Fix re replace issue Fixes #467 * Re-generate docs * Bump version
1 parent 44c1c69 commit 657ae18

File tree

15 files changed

+154
-23
lines changed

15 files changed

+154
-23
lines changed

docs/src/markdown/about/changelog.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 4.21.1
4+
5+
- **FIX**: Fix issue with `re` replace in Python 3.12 and Python 3.13.
6+
37
## 4.21
48

59
- **NEW**: Officially support Python 3.12 and Python 3.13, requires latest wxPython (4.2.2).

docs/src/markdown/preferences.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ The **General** tab contains a couple of useful settings.
1515
Single Instance
1616

1717
- By default, Rummage will allow for multiple windows to be open. If this option is enabled, the first window will be
18-
be the only window to open. All subsequent instances will pass their arguments to the first and close without
19-
showing a window.
18+
the only window to open. All subsequent instances will pass their arguments to the first and close without showing
19+
a window.
2020

2121
Language
2222

docs/src/markdown/usage.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ options.
108108

109109
/// tip | Column Options
110110
You can hide/show columns by right clicking the list header to get a special context menu. You can then deselect or
111-
select the the column(s) you wish to hide/show respectively. You can also reorder the columns if desired.
111+
select the column(s) you wish to hide/show respectively. You can also reorder the columns if desired.
112112
///
113113

114114
## Regular Expression Tester

rummage/lib/__meta__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ def parse_version(ver):
189189

190190

191191
# (major, minor, micro, release type, pre-release build, post-release build, development-release)
192-
__version_info__ = Version(4, 21, 0, 'final')
192+
__version_info__ = Version(4, 21, 1, 'final')
193193
__version__ = __version_info__._get_canonical()
194194
__app__ = "Rummage"
195195
__status__ = __version_info__[3]

rummage/lib/gui/controls/custom_statusbar.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class ContextMenu(wx.Menu):
2929
"""Context Menu."""
3030

3131
def __init__(self, menu):
32-
"""Attach the context menu to to the parent with the defined items."""
32+
"""Attach the context menu to the parent with the defined items."""
3333

3434
wx.Menu.__init__(self)
3535
self._callbacks = {}

rummage/lib/gui/controls/dynamic_lists.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ def set_item_map(self, idx, *args):
298298
self.size_sample -= 1
299299

300300
def get_map_item(self, idx, col=0, absolute=False):
301-
"""Get attribute in in item map entry and the given index."""
301+
"""Get attribute in item map entry and the given index."""
302302

303303
return self.itemDataMap[self.itemIndexMap[idx] if not absolute else idx][self.get_real_col(col)]
304304

rummage/lib/gui/controls/result_lists.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ class ContextMenu(wx.Menu):
6464
"""Context Menu."""
6565

6666
def __init__(self, menu):
67-
"""Attach the context menu to to the parent with the defined items."""
67+
"""Attach the context menu to the parent with the defined items."""
6868

6969
wx.Menu.__init__(self)
7070
self.create_menu(self, menu)

rummage/lib/gui/data/docs/.dochash

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0cd58713b67eafdcd920f80223baa5dc
1+
ff7edfad8ee90d27d9cbc711c759338c

rummage/lib/gui/data/docs/about/changelog.html

+4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525

2626
<!-- Table of Content (Don't show for static sites) -->
2727
<h1 id="changelog">Changelog</h1>
28+
<h2 id="4211">4.21.1</h2>
29+
<ul>
30+
<li><strong>FIX</strong>: Fix issue with <code>re</code> replace in Python 3.12 and Python 3.13.</li>
31+
</ul>
2832
<h2 id="421">4.21</h2>
2933
<ul>
3034
<li><strong>NEW</strong>: Officially support Python 3.12 and Python 3.13, requires latest wxPython (4.2.2).</li>

rummage/lib/gui/data/docs/preferences.html

+2-2
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ <h2 id="general">General</h2>
8484
<dl>
8585
<dt>Single Instance</dt>
8686
<dd>By default, Rummage will allow for multiple windows to be open. If this option is enabled, the first window will be
87-
be the only window to open. All subsequent instances will pass their arguments to the first and close without
88-
showing a window.</dd>
87+
the only window to open. All subsequent instances will pass their arguments to the first and close without showing
88+
a window.</dd>
8989
<dt>Language</dt>
9090
<dd>Rummage has internal support to display dialog labels in different languages. Currently Rummage has English. Russian
9191
is outdated but includes a fair bit of the needed translations. See <a href="extras.html#localization">Localization</a> to

rummage/lib/gui/data/docs/usage.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ <h3 id="results">Results</h3>
162162
<div class="admonition tip">
163163
<p class="admonition-title">Column Options</p>
164164
<p>You can hide/show columns by right clicking the list header to get a special context menu. You can then deselect or
165-
select the the column(s) you wish to hide/show respectively. You can also reorder the columns if desired.</p>
165+
select the column(s) you wish to hide/show respectively. You can also reorder the columns if desired.</p>
166166
</div>
167167
<h2 id="regular-expression-tester">Regular Expression Tester</h2>
168168
<p><img alt="Regex Tester" src="images/regex_tester.png" /></p>

rummage/lib/gui/dialogs/rummage_dialog.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,7 @@ def localize(self):
531531
self.ERR_UPDATE = _("There was an error checking for updates!")
532532
self.ERR_INVALID_SEARCH_PTH = _("Please enter a valid search path!")
533533
self.ERR_INVALID_SEARCH = _("Please enter a valid search!")
534-
self.ERR_EMPTY_CHAIN = _("There are no searches in this this chain!")
534+
self.ERR_EMPTY_CHAIN = _("There are no searches in this chain!")
535535
self.ERR_MISSING_SEARCH = _("'%s' is not found in saved searches!")
536536
self.ERR_INVALID_CHAIN = _("Please enter a valid chain!")
537537
self.ERR_INVALID_CHAIN_SEARCH = _("Saved search '%s' does not contain a valid search pattern!")

rummage/lib/gui/util/colors.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class Color(Base):
1818
"""Color object."""
1919

2020
def to_wxbgr(self, alpha=True):
21-
"""Get the the wxPython RGB value."""
21+
"""Get the wxPython RGB value."""
2222

2323
r, g, b = [alg.clamp(int(alg.round_half_up(c * 255)), 0, 255) for c in self.convert('srgb').coords(nans=False)]
2424
a = alg.clamp(int(alg.round_half_up(self.alpha(nans=False)))) if alpha else 0xFF

rummage/lib/rumcore/__init__.py

+2-10
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,6 @@
3939
except ImportError: # pragma: no cover
4040
REGEX_SUPPORT = False
4141

42-
if sys.version_info >= (3, 11):
43-
import re._parser as _parser
44-
else:
45-
import sre_parse as _parser
46-
4742
# Common regex flags (re|regex)
4843
IGNORECASE = 0x1 # (?i)
4944
DOTALL = 0x2 # (?s)
@@ -309,7 +304,7 @@ class Search:
309304
"""Search setup object."""
310305

311306
def __init__(self, replace=False):
312-
"""Setup search object as as a search only or search and replace object."""
307+
"""Setup search object as a search only or search and replace object."""
313308

314309
self._entry = []
315310
self._is_replace = replace
@@ -747,9 +742,6 @@ def _findall(self, file_content, search_pattern, replace_pattern, flags, file_in
747742
self.expand = pattern.compile(replace, (bre.FORMAT if bool(flags & FORMATREPLACE) else 0))
748743
else:
749744
pattern = _re_pattern(pattern, flags, self.is_binary)
750-
if replace is not None and not self.is_plugin_replace:
751-
template = _parser.parse_template(replace, pattern)
752-
self.expand = lambda m, t=template: _parser.expand_template(t, m)
753745

754746
if REGEX_SUPPORT and isinstance(pattern, (bregex._REGEX_TYPE, bregex.Bregex)):
755747
self.reverse = bool(pattern.flags & regex.REVERSE)
@@ -981,7 +973,7 @@ def search_and_replace(self):
981973

982974
if not self.abort and text:
983975
# Update the file or buffer depending on what is being used.
984-
# For a buffer, we will actually return the the content via a `BufferRecord`.
976+
# For a buffer, we will actually return the content via a `BufferRecord`.
985977
if is_buffer:
986978
yield self._update_buffer(text)
987979
file_record_sent = True

tests/test_rumcore.py

+131
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,137 @@ def test_literal_chain_search(self):
956956
print(results)
957957
self.assertEqual(len(results), 4)
958958

959+
def test_re_chain_replace(self):
960+
"""Test for `re` search and replace."""
961+
962+
self.mktemp(
963+
'searches.txt',
964+
content=self.dedent(
965+
'''
966+
search1
967+
search1
968+
969+
search2
970+
search2
971+
972+
search3
973+
search3
974+
975+
search1, search2, search3
976+
'''
977+
).encode('utf-8')
978+
)
979+
980+
after = self.dedent(
981+
'''
982+
replace1
983+
replace1
984+
985+
replace2
986+
replace2
987+
988+
search3
989+
search3
990+
991+
replace1, replace2, search3
992+
'''
993+
)
994+
995+
search_params = rc.Search(True)
996+
search_params.add('search1', 'replace1', rc.IGNORECASE)
997+
search_params.add('search2', 'replace2', rc.IGNORECASE)
998+
999+
file_id = 0
1000+
encoding = None
1001+
context = (0, 0)
1002+
flags = 0
1003+
backup_ext = 'rum-bak',
1004+
max_count = None
1005+
1006+
fs = rc._FileSearch(
1007+
search_params,
1008+
self.get_file_attr('searches.txt'),
1009+
file_id,
1010+
flags,
1011+
context,
1012+
encoding,
1013+
backup_ext,
1014+
max_count
1015+
)
1016+
1017+
for result in fs.run():
1018+
if result.error is not None:
1019+
print(''.join(result.error))
1020+
1021+
with codecs.open(self.norm('searches.txt'), 'r', encoding='utf-8') as f:
1022+
self.assertEqual(f.read(), after)
1023+
1024+
def test_regex_chain_replace(self):
1025+
"""Test for `regex` search and replace."""
1026+
1027+
self.mktemp(
1028+
'searches.txt',
1029+
content=self.dedent(
1030+
'''
1031+
search1
1032+
search1
1033+
1034+
search2
1035+
search2
1036+
1037+
search3
1038+
search3
1039+
1040+
search1, search2, search3
1041+
'''
1042+
).encode('utf-8')
1043+
)
1044+
1045+
after = self.dedent(
1046+
'''
1047+
replace1
1048+
replace1
1049+
1050+
replace2
1051+
replace2
1052+
1053+
search3
1054+
search3
1055+
1056+
replace1, replace2, search3
1057+
'''
1058+
)
1059+
1060+
search_params = rc.Search(True)
1061+
search_params.add('search1', 'replace1', rc.IGNORECASE)
1062+
search_params.add('search2', 'replace2', rc.IGNORECASE)
1063+
1064+
file_id = 0
1065+
encoding = None
1066+
context = (0, 0)
1067+
flags = 0
1068+
backup_ext = 'rum-bak',
1069+
max_count = None
1070+
1071+
fs = rc._FileSearch(
1072+
search_params,
1073+
self.get_file_attr('searches.txt'),
1074+
file_id,
1075+
flags,
1076+
context,
1077+
encoding,
1078+
backup_ext,
1079+
max_count,
1080+
regex_mode=rc.REGEX_MODE
1081+
)
1082+
1083+
for result in fs.run():
1084+
if result.error is not None:
1085+
print(''.join(result.error))
1086+
1087+
with codecs.open(self.norm('searches.txt'), 'r', encoding='utf-8') as f:
1088+
self.assertEqual(f.read(), after)
1089+
9591090
def test_literal_chain_replace(self):
9601091
"""Test for literal search and replace."""
9611092

0 commit comments

Comments
 (0)