This is a much more basic implementation of Regular Expressions (Python docs). Do note that core functionality is missing and the functions aren't completely fool-proof, but it does a good job of matching basic patterns. Also, filtering 'symmetric' patterns (like quotation marks "" or markdown bold **) is not possible.
That being said, here's the code:
"""
Module for quick and efficient pattern filtering.
Similar to Regular Expressions (https://docs.python.org/3.3/library/re.html);
but with a goal of being easier to use. Note that lots of features that are available in re
are *not* available in this module. An example of supported operations:
- Filtering everything in parentheses () in a string
- Filtering every first occurrence of a word in parentheses () or square brackets []
- Tagging every verb in a string with angle brackets <> (given a list of verbs!)
An example of unsupported operations:
- Filtering email addresses from text
- Retrieving text embedded in quotation marks ''
- Complex text / lexical analysis
"""
class Crucial:
"""Basic interface for pattern matching and tagging.
You are recommended to use the Scope class for interacting with this interface.
Contents:
* _asymmetric(): Asymmetric pattern matching
* _tag(): Asymmetric pattern tagging
* _beautify(): Beautify results (append to list)
"""
def __init__(self):
pass
def _asymmetric(self, _raw, _start, _end, _filter_first):
"""Provides a backend interface for asymmetric pattern matching.
Works for a 'raw' string, along with a single start / end character.
Arguments:
* _raw: String containing patterns to filter (e.g. 'spam [eggs]')
* _start: Pattern start character (e.g. '[')
* _end: Pattern end character (e.g. ']')
* _filter_first: Argument to filter only the first pattern found.
"""
final = []
temp, sort = [], []
for pos, val in enumerate(_raw):
if val == _start:
temp.append([pos])
elif val == _end:
next = True
for index in temp:
if _raw[index[0]] == _start:
next = False
# This avoids match collisions (missing end character)
if not next:
temp[temp.index(max(temp))].append(pos)
# Find the latest (max) pattern
sort.append(max(temp))
temp.pop(temp.index(max(temp)))
for val in sorted(sort):
final.append(_raw[val[0] + 1:val[-1]])
if _filter_first:
break
return final
def _tag(self, _raw, _tags, _seperator):
"""Provides a backend interface for asymmetric pattern tagging.
Works for a 'raw' string, along with a tag dictionary and seperator character.
Arguments:
* _raw: String containing all words, not just those to be matched.
* _tags: Dictionary containing key:value pairs like "spam":"[]" where every
"spam" will be enclosed by "[]" ("[spam]")
* _seperator: Word seperator, commonly " " or ", " for a comma-seperated list.
"""
final = ""
for val in _raw.split(_seperator):
if val not in _tags:
final += val + _seperator
else:
final += _tags[val][0] + val + _tags[val][1]
final += _seperator
return final
def _beautify(self, _filtered, _allow_nested):
"""Beautify filtered patterns by appending them to a list.
Works for 'filtered' dictionaries.
Arguments:
* _filtered: Dictionary with results matched by start / end character
* _allow_nested: Boolean, if True, all results will be returned in a list or printed.
Else, only 'pure' results (results containing no nested patterns), are returned.
"""
def is_nested(frm):
# Check if a frame is pure, treating it as 'raw'
result = Scope().auto(frm)
for pattern_ in result:
for frame_ in result[pattern_]:
if len(frame_) > 0:
return True
return False
final = []
for pattern in _filtered:
for frame in _filtered[pattern]:
if not _allow_nested:
if not is_nested(frame):
final.append(frame)
continue
continue
final.append(frame)
return final
class Scope:
"""Main frontend interface for pattern matching / tagging.
Contents:
* _err(): Function for constructing error messages
* match(): Allows asymmetric pattern matching
* tag(): Tag specific words in a raw string
* auto(): Automatically match patterns
* beautify(): Wrapper for backend _beautify()
"""
def __init__(self):
self.RED = "\033[91m"
self.GRN = "\033[92m"
self.YLW = "\033[93m"
self.WHT = "\033[00m"
def _err(self, message):
"""Print errors to the screen."""
print("{}{}{}".format(self.RED, message, self.WHT))
def match(self, raw, *args, filter_first=False):
"""Match patterns based on asymmetric characters.
match() will always return a dictionary with key:value pairs like
pattern_start + pattern_end : [returned, items, seperatd, with, commas].
Arguments:
* raw: A raw string to be inspected for patterns
* *args: Any amount of arguments (patterns), in the form of
"<>", "[]", "()". For example: match("spam [eggs]", "[]")
* filter_frames: Optional argument to filter only the first frame of each pattern.
"""
final = {}
for arg in args:
# arg represents a pattern.
start = arg[0]
end = arg[1]
collision = (start + end in args and end + start in args)
if collision:
self._err("Collision: {}{} / {}{}".format(start, end, end, start))
return
final[start + end] = Crucial()._asymmetric(raw, start, end, filter_first)
return final
def tag(self, raw, tags, seperator=" "):
"""Encapsulate words based on a 'tags' enum.
Arguments:
* raw: A raw string containing words to be tagged
* tags: Dictionary containing key:value pairs like "spam":"[]".
Each "spam" in raw will be surrounded by "[" ... "]".
* seperator: Optional argument for seperating words (string.split()).
Defaults to a single space " ".
"""
return Crucial()._tag(raw, tags, seperator)
def auto(self, raw):
"""Automatically match frames in a string, based on common patterns.
auto() will always return a dictionary with key:value pairs like
pattern_start + pattern_end : [returned, items, seperatd, with, commas].
Arguments:
* raw: A raw string to be inspected for patterns
"""
final = {}
temp = []
for pattern in ["()", "[]", "<>", "{}"]:
if pattern[0] in raw and pattern[1] in raw:
result = Scope().match(raw, pattern)
for pattern_ in result:
final[pattern_] = result[pattern_]
return final
def beautify(self, filtered, allow_nested=True):
"""Beautify matched patterns.
Arguments:
* filtered: Dictionary containing filtered patterns
* allow_nested: If set to True, nested patterns are allowed.
Else, only 'pure' patterns are used.
"""
return Crucial()._beautify(filtered, allow_nested)
And here's some examples:
import scope
scope = scope.Scope()
easy = "The (quick) (grey) [fox] jumps over the (lazy) (white) [dog]"
hard = """
This is (level 0 and (this is level 1. (Level 2 is especially (interesting) for pattern matching!)
[this is additional information, although it doesn't help you understand what is going on.))
"""
print(scope.match(easy, "()"))
# Match adjectives
print(scope.match(easy, "[]"))
# Match nouns
print(scope.auto(easy))
# Match both nouns and adjectives
print(scope.beautify(scope.auto(easy)))
# Convert the results to a list and print it
print(scope.beautify(scope.auto(hard), allow_nested=False))
# Print only 'pure' patterns
print(scope.tag(hard, {"is":"<>"}))
# Tag all verbs 'is' with angle brackets
# etc.
I would appreciate any advice / critisism regarding coding conventions or style, performance, usability, etc.