Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge extended back reference from ST3

- Also, modify plugin system and add example
  • Loading branch information...
commit 7a3cd88f1d5fd26710b6cbfb5489ad6fd740f360 1 parent 30b991a
@facelessuser authored
View
5 reg_replace.sublime-settings
@@ -122,5 +122,8 @@
"find_highlight_style": "outline",
// Search under selection(s) if and only if exists
- "selection_only": false
+ "selection_only": false,
+
+ // Use extended backreferences
+ "extended_back_references": false
}
View
296 rr_extended.py
@@ -0,0 +1,296 @@
+import sre_parse
+import traceback
+
+CAP_TOKEN = set("lLcCE")
+DEF_BACK_REF = set("abfnrtvAbBdDsSwWZuxg")
+
+
+class ReplaceTemplate(object):
+ def __init__(self, pattern, template):
+ self.__original = template
+ self.__back_ref = set()
+ self.__add_back_references(CAP_TOKEN)
+ self.__template = self.__escape_template(template)
+ self.groups, self.literals = sre_parse.parse_template(self.__template, pattern)
+
+ def get_base_template(self):
+ return self.__original
+
+ def __escape_template(self, template):
+ new_template = []
+ slash_count = 0
+ for c in template:
+ if c == "\\":
+ slash_count += 1
+ elif c in self.__back_ref:
+ if slash_count != 0 and (slash_count % 2) == 0:
+ new_template.append("\\")
+ slash_count = 0
+ else:
+ slash_count = 0
+ new_template.append(c)
+ return "".join(new_template)
+
+ def __add_back_references(self, args):
+ for arg in args:
+ if isinstance(arg, str) and len(arg) == 1:
+ if arg not in DEF_BACK_REF and arg not in self.__back_ref:
+ self.__back_ref.add(arg)
+
+ def get_group_index(self, index):
+ """
+ Find and return the appropriate group index
+ """
+
+ g_index = None
+ for group in self.groups:
+ if group[0] == index:
+ g_index = group[1]
+ break
+ return g_index
+
+
+class Tokens(object):
+ def __init__(self, string):
+ self.string = string
+ self.last = len(string) - 1
+ self.index = 0
+ self.current = None
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ """
+ Iterate through characters of the string.
+ Count \l, \L, \c, \C and \\ as a single char.
+ """
+
+ if self.index > self.last:
+ raise StopIteration
+
+ char = self.string[self.index]
+ if char == "\\":
+ try:
+ c = self.string[self.index + 1]
+ if c in CAP_TOKEN:
+ char += c
+ elif c == "\\":
+ try:
+ ref = self.string[self.index + 2]
+ if ref in CAP_TOKEN:
+ self.index += 1
+ else:
+ char += c
+ except:
+ char += c
+ pass
+ except:
+ pass
+
+ self.index += len(char)
+ self.current = char
+ return self.current
+
+
+class TitleCase(object):
+ def __init__(self, match, template):
+ self.template = template
+ self.upper = False
+ self.lower = False
+ self.span = False
+ self.match = match
+ self.text = []
+
+ def group_entry(self):
+ """
+ Insert the correct group into the next slot.
+ If we are currently adjusting case, make the appropriate,
+ alteration for the group. Allow adjustments to span past
+ the current group if needed.
+ """
+
+ g_index = self.template.get_group_index(self.index)
+ if g_index is not None:
+ if self.upper:
+ # Upper case adjustment
+ if self.span:
+ # Span
+ self.text.append(self.match.group(g_index).upper())
+ else:
+ # Single char
+ g_str = self.match.group(g_index)
+ if len(g_str):
+ # No character to adjust
+ self.text.append(g_str[0].upper() + g_str[1:])
+ self.upper = False
+ elif self.lower:
+ # Lower case adjustment
+ if self.span:
+ # Single char
+ self.text.append(self.match.group(g_index).lower())
+ else:
+ g_str = self.match.group(g_index)
+ if len(g_str):
+ # No character to adjust
+ self.text.append(g_str[0].lower() + g_str[1:])
+ self.lower = False
+ else:
+ # Copy the entire group
+ self.text.append(self.match.group(g_index))
+
+ def span_upper(self, i, new_entry, c=None):
+ """
+ Uppercase the next range of characters until end marker is found.
+ Allow spanning past the current group if needed.
+ """
+
+ try:
+ if c is None:
+ c = next(i)
+ while c != "\\E":
+ new_entry.append(c.upper())
+ c = next(i)
+ self.upper = False
+ self.span = False
+ except StopIteration:
+ self.upper = True
+ self.span = True
+
+ def span_lower(self, i, new_entry, c=None):
+ """
+ Lowercase the next range of characters until end marker is found.
+ Allow spanning past the current group if needed.
+ """
+
+ try:
+ if c is None:
+ c = next(i)
+ while c != "\\E":
+ new_entry.append(c.lower())
+ c = next(i)
+ self.lower = False
+ self.span = False
+ except StopIteration:
+ self.lower = True
+ self.span = True
+
+ def single_lower(self, i, new_entry):
+ """
+ Lowercase the next character.
+ If none found, allow spanning to the next group.
+ """
+
+ try:
+ t = next(i)
+ if len(t) > 1:
+ new_entry.append(t)
+ else:
+ new_entry.append(t.lower())
+ self.lower = False
+ except StopIteration:
+ self.lower = True
+
+ def single_upper(self, i, new_entry):
+ """
+ Lowercase the next character.
+ If none found, allow spanning to the next group.
+ """
+
+ try:
+ t = next(i)
+ if len(t) > 1:
+ new_entry.append(t)
+ else:
+ new_entry.append(t.upper())
+ self.upper = False
+ except StopIteration:
+ self.upper = True
+
+ def string_entry(self, entry):
+ """
+ Parse the string entry and find title case backreferences.
+ Make necessary adjustments if needed.
+ """
+
+ new_entry = []
+ i = Tokens(entry)
+ for t in i:
+ if t is None:
+ break
+ if len(t) > 1 and not self.upper and not self.lower:
+ # Backreference has been found
+ # This is for the neutral state
+ # (currently applying no title cases)
+ c = t[1]
+ if c == "\\":
+ new_entry.append(t)
+ elif c == "E":
+ new_entry.append(t)
+ elif c == "l":
+ self.single_lower(i, new_entry)
+ elif c == "L":
+ self.span_lower(i, new_entry)
+ elif c == "c":
+ self.single_upper(i, new_entry)
+ elif c == "C":
+ self.span_upper(i, new_entry)
+ else:
+ # This is for normal characters or when in
+ # the active state (currently applying title case)
+ if self.upper:
+ if self.span:
+ self.span_upper(i, new_entry, t)
+ else:
+ self.single_upper(i, new_entry)
+ elif self.lower:
+ if self.span:
+ self.span_lower(i, new_entry, t)
+ else:
+ self.single_lower(i, new_entry)
+ else:
+ new_entry.append(t)
+
+ # Add the newly formatted string
+ if len(new_entry):
+ self.text.append("".join(new_entry))
+ new_entry = []
+
+ def expand_titles(self):
+ """
+ Parse the template and construct the expanded
+ string with appropriate title cases.
+ """
+
+ for x in range(0, len(self.template.literals)):
+ self.index = x
+ entry = self.template.literals[x]
+ if entry is None:
+ # Empty slot, find the group that
+ # should fill this spot
+ self.group_entry()
+ else:
+ # Parse the literal string
+ # and search for upper and lower
+ # case backreferences.
+ # Apply title case if needed.
+ self.string_entry(entry)
+ return "".join(self.text)
+
+
+def replace(m, template):
+ """
+ Replace event. Using the template, scan for (\c | \C.*?\E | \l | \L.*?\E).
+ (c|C) are capital/upper case. (l|L) is lower case.
+ \c and \l are applied to the next character. While \C and \L are applied to
+ all characters until either the end of the string is found, or the end marker \E
+ is found. Actual group content is not scanned for markers.
+ """
+
+ assert isinstance(template, ReplaceTemplate), "Not a valid template!"
+
+ try:
+ return TitleCase(m, template).expand_titles()
+ except:
+ print(str(traceback.format_exc()))
+ return sre_parse.expand_template(template.get_base_template(), m)
View
6 rr_modules/example.py
@@ -0,0 +1,6 @@
+def replace(m, **kwargs):
+ text = "Here are your groups: "
+ for group in m.groups():
+ if group is not None:
+ text += "(%s)" % group
+ return text
View
15 rr_modules/plugin_chain.py
@@ -1,15 +0,0 @@
-from RegReplace.rr_plugin import Plugin
-import traceback
-
-
-def run(text, chain=[]):
- for c in chain:
- module_name = c.get("plugin", None)
- if module_name is not None:
- try:
- module = Plugin.load(module_name)
- except:
- print(str(traceback.format_exc()))
- if module is not None:
- text = module.run(text, c.get("args", {}))
- return text
View
73 rr_replacer.py
@@ -1,5 +1,6 @@
import sublime
import re
+import rr_extended
import traceback
from rr_plugin import Plugin
@@ -20,6 +21,8 @@ def __init__(self, view, edit, find_only, full_file, selection_only, max_sweeps,
self.action = action
self.target_regions = []
self.plugin = None
+ settings = sublime.load_settings('reg_replace.sublime-settings')
+ self.extend = bool(settings.get("extended_back_references", False))
def close(self):
"""
@@ -28,19 +31,17 @@ def close(self):
Plugin.purge()
- def run_plugin(self, text):
+ def on_replace(self, m):
"""
- Run the associated plugin on text
+ Run the associated plugin on the replace event
"""
- if self.plugin is not None:
- module = None
- try:
- module = Plugin.load(self.plugin)
- except:
- print(str(traceback.format_exc()))
- if module is not None:
- text = module.run(text, **self.plugin_args)
+ try:
+ module = Plugin.load(self.plugin)
+ text = module.replace(m, **self.plugin_args)
+ except:
+ text = m.group(0)
+ print(str(traceback.format_exc()))
return text
def filter_by_selection(self, regions, extractions=None):
@@ -146,7 +147,7 @@ def greedy_replace(self, find, replace, regions, scope_filter):
self.target_regions.append(region)
else:
# Apply replace
- self.view.replace(self.edit, region, self.run_plugin(replace[count]))
+ self.view.replace(self.edit, region, replace[count])
count -= 1
return replaced
@@ -206,9 +207,15 @@ def non_greedy_replace(self, find, replace, regions, scope_filter):
self.target_regions.append(selected_region)
else:
# Apply replace
- self.view.replace(self.edit, selected_region, self.run_plugin(replace[selection_index]))
+ self.view.replace(self.edit, selected_region, replace[selection_index])
return replaced
+ def expand(self, m, replace):
+ if self.extend:
+ return rr_extended.replace(m, self.template)
+ else:
+ return m.expand(replace)
+
def regex_findall(self, find, flags, replace, extractions, literal=False, sel=None):
"""
Findall with regex
@@ -224,9 +231,15 @@ def regex_findall(self, find, flags, replace, extractions, literal=False, sel=No
flags |= re.MULTILINE
if literal:
find = re.escape(find)
- for m in re.compile(find, flags).finditer(bfr):
+ pattern = re.compile(find, flags)
+ if self.extend:
+ self.template = rr_extended.ReplaceTemplate(pattern, replace)
+ for m in pattern.finditer(bfr):
regions.append(sublime.Region(offset + m.start(0), offset + m.end(0)))
- extractions.append(m.expand(replace))
+ if self.plugin is not None:
+ extractions.append(self.on_replace(m))
+ else:
+ extractions.append(self.expand(m, replace))
return regions
def apply(self, pattern):
@@ -274,6 +287,7 @@ def apply(self, pattern):
else:
regions = self.regex_findall(find, flags, replace, extractions, literal)
except Exception as err:
+ print(str(traceback.format_exc()))
sublime.error_message('REGEX ERROR: %s' % str(err))
return replaced
@@ -309,16 +323,23 @@ def apply_scope_regex(self, string, re_find, replace, greedy_replace, multi):
replaced = 0
extraction = string
+ if self.plugin is None:
+ repl = lambda m, replace=replace: self.expand(m, replace)
+ else:
+ repl = self.on_replace
+ pattern = re.compile(re_find)
+ if self.extend:
+ self.template = rr_extended.ReplaceTemplate(pattern, replace)
if multi and not self.find_only and self.action is None:
- extraction, replaced = self.apply_multi_pass_scope_regex(string, extraction, re_find, replace, greedy_replace)
+ extraction, replaced = self.apply_multi_pass_scope_regex(pattern, string, extraction, repl, greedy_replace)
else:
if greedy_replace:
- extraction, replaced = re.subn(re_find, replace, string)
+ extraction, replaced = pattern.subn(repl, string)
else:
- extraction, replaced = re.subn(re_find, replace, string, 1)
+ extraction, replaced = pattern.subn(repl, string, 1)
return extraction, replaced
- def apply_multi_pass_scope_regex(self, string, extraction, re_find, replace, greedy_replace):
+ def apply_multi_pass_scope_regex(self, pattern, string, extraction, repl, greedy_replace):
"""
Use a multi-pass scope regex
"""
@@ -329,9 +350,9 @@ def apply_multi_pass_scope_regex(self, string, extraction, re_find, replace, gre
while count < self.max_sweeps:
count += 1
if greedy_replace:
- extraction, multi_replaced = re.subn(re_find, replace, extraction)
+ extraction, multi_replaced = pattern.subn(repl, extraction)
else:
- extraction, multi_replaced = re.subn(re_find, replace, extraction, 1)
+ extraction, multi_replaced = pattern.subn(repl, extraction, 1)
if multi_replaced == 0:
break
total_replaced += multi_replaced
@@ -360,7 +381,7 @@ def greedy_scope_literal_replace(self, regions, find, replace, greedy_replace):
if self.find_only or self.action is not None:
self.target_regions.append(region)
else:
- self.view.replace(self.edit, region, self.run_plugin(extraction))
+ self.view.replace(self.edit, region, extraction)
return total_replaced
def non_greedy_scope_literal_replace(self, regions, find, replace, greedy_replace):
@@ -435,7 +456,7 @@ def non_greedy_scope_literal_replace(self, regions, find, replace, greedy_replac
self.target_regions.append(selected_region)
else:
# Apply replace
- self.view.replace(self.edit, selected_region, self.run_plugin(selected_extraction))
+ self.view.replace(self.edit, selected_region, selected_extraction)
return total_replaced
def greedy_scope_replace(self, regions, re_find, replace, greedy_replace, multi):
@@ -454,8 +475,9 @@ def greedy_scope_replace(self, regions, re_find, replace, greedy_replace, multi)
if self.find_only or self.action is not None:
self.target_regions.append(region)
else:
- self.view.replace(self.edit, region, self.run_plugin(extraction))
+ self.view.replace(self.edit, region, extraction)
except Exception as err:
+ print(str(traceback.format_exc()))
sublime.error_message('REGEX ERROR: %s' % str(err))
return total_replaced
@@ -489,6 +511,7 @@ def non_greedy_scope_replace(self, regions, re_find, replace, greedy_replace, mu
else:
count += 1
except Exception as err:
+ print(str(traceback.format_exc()))
sublime.error_message('REGEX ERROR: %s' % str(err))
return total_replaced
@@ -510,6 +533,7 @@ def non_greedy_scope_replace(self, regions, re_find, replace, greedy_replace, mu
else:
break
except Exception as err:
+ print(str(traceback.format_exc()))
sublime.error_message('REGEX ERROR: %s' % str(err))
return total_replaced
@@ -523,7 +547,7 @@ def non_greedy_scope_replace(self, regions, re_find, replace, greedy_replace, mu
self.target_regions.append(selected_region)
else:
# Apply replace
- self.view.replace(self.edit, selected_region, self.run_plugin(selected_extraction))
+ self.view.replace(self.edit, selected_region, selected_extraction)
return total_replaced
def select_scope_regions(self, regions, greedy_scope):
@@ -617,6 +641,7 @@ def scope_apply(self, pattern):
flags |= re.DOTALL
re_find = re.compile(find, flags)
except Exception as err:
+ print(str(traceback.format_exc()))
sublime.error_message('REGEX ERROR: %s' % str(err))
return replaced
Please sign in to comment.
Something went wrong with that request. Please try again.