├── .gitignore ├── .travis.yml ├── LICENSE ├── README.rst ├── examples ├── SpeechRecognition.py ├── find.py ├── int_parse.py ├── interator_find.py ├── interator_parse.py ├── multiple_words.py └── unicode.py ├── requirements.txt ├── setup.py ├── test ├── Test1.txt └── test_parse.py └── tpfd ├── __init__.py ├── compat.py ├── parser.py └── rules.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | #Ipython Notebook 62 | .ipynb_checkpoints 63 | /.vs/tpfd/v14 64 | *.pyproj 65 | *.sln 66 | *.user 67 | *.user 68 | 69 | .gitattributes 70 | .vs/Text-Parsing-Function-Dispatcher/v14/.suo 71 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.6" 4 | - "2.7" 5 | - "3.3" 6 | - "3.4" 7 | - "3.5" 8 | - "pypy" 9 | - "pypy3" 10 | # command to install dependencies 11 | install: 12 | - pip install -r requirements.txt 13 | - python setup.py install 14 | # command to run tests 15 | script: 16 | - cd test 17 | - python test_parse.py -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Erin O'Connell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | TPFD - Text Parsing Function Dispatcher 2 | ======================================= 3 | .. image:: https://travis-ci.org/erinxocon/tpfd.svg?branch=master 4 | :target: https://travis-ci.org/erinxocon/tpfd 5 | .. image:: https://img.shields.io/pypi/v/tpfd.svg?maxAge=2592000 6 | :target: https://pypi.python.org/pypi/tpfd/ 7 | .. image:: https://img.shields.io/pypi/l/tpfd.svg?maxAge=2592000 8 | :target: https://opensource.org/licenses/MIT 9 | 10 | **TPFD** is an easy way to parse strings and execute functions depending on their contents. 11 | 12 | Inspired by `Flask `_ and using `Parse `_ under the hood, this allows you to decorate functions with grammar rules and if a pattern that matches one of your grammar rules is found, the function will be run with a set of keyword arguments you've specified passed to it! Great for parsing logs and executing macros on what it finds! 13 | 14 | Examples 15 | -------- 16 | .. code-block:: python 17 | 18 | Aniamls.txt 19 | Turtles are cool 20 | Sloths are cool 21 | Mosquitos are dumb 22 | 23 | >>> p = tpfd.Parser() 24 | 25 | >>> @p.on_parse('{Animal} are cool') 26 | def main(animal): 27 | print('I like {0}.'.format(animal)) 28 | 29 | >>> p.parse_file('animals.txt') 30 | 'I like turtles.' 31 | 'I like sloths.' 32 | 33 | >>> p.parse(['Turtles are cool', 'Sloths are cool', 'Mosquitos are dumb']) 34 | 'I like turtles.' 35 | 'I like sloths.' 36 | 37 | >>> p.parse('Sloths are cool') 38 | 'I like sloths.' 39 | 40 | >>> p.parse('Mosquitos are dumb') 41 | None 42 | 43 | >>> @p.on_find('>{}<') 44 | def find_example(words): 45 | print (words) 46 | 47 | >>> p.find('

the bold text

') 48 | 'the bold text' 49 | 50 | To Install 51 | ---------- 52 | 53 | :: 54 | 55 | $ pip install tpfd 56 | 57 | Notes 58 | ----- 59 | Any format spec supported by parse is supported by this library since it's all parse under the hood. 60 | Example: ``{[field name]:[format spec]}`` 61 | 62 | Current Features 63 | ---------------- 64 | 65 | * Support for parsing text files 66 | * Support for accepting generators that output text or ints 67 | * Support for parsing unicode strings 68 | * Supports parsing strings, ints and interator/generator's automagically with new ``parse`` method. 69 | 70 | 71 | TODO 72 | ---- 73 | * Expose custom types functionality that `Parse `_ already offers 74 | -------------------------------------------------------------------------------- /examples/SpeechRecognition.py: -------------------------------------------------------------------------------- 1 | import speech_recognition as sr 2 | import tpfd 3 | 4 | #instantiate parser 5 | p = tpfd.Parser() 6 | 7 | #Set up basic rule that looks for the string blank song, 8 | #e.x. play song, next song, previous song 9 | @p.on_parse('{Action} song') 10 | def main(action): 11 | print(action) 12 | 13 | # obtain audio from the microphone 14 | r = sr.Recognizer() 15 | with sr.Microphone() as source: 16 | print("Say something!") 17 | r.adjust_for_ambient_noise(source) 18 | audio = r.listen(source) 19 | 20 | #try and recognize the captured audio and pass the recognized 21 | #phrase to the parse method 22 | try: 23 | string = r.recognize_google(audio) 24 | print("Google Speech Recognition thinks you said " + string) 25 | p.parse(string) -------------------------------------------------------------------------------- /examples/find.py: -------------------------------------------------------------------------------- 1 | import tpfd 2 | 3 | #instantiate parser 4 | p = tpfd.Parser() 5 | 6 | #set up find rule, in this case anything inbetweeen a > and < 7 | @p.on_find('>{}<') 8 | def find_example(words): 9 | print (words) 10 | 11 | #parse a string to evaluate against find rules 12 | def find_string(): 13 | p.find('

the bold text

') 14 | 15 | 16 | if __name__ == '__main__': 17 | find_string() -------------------------------------------------------------------------------- /examples/int_parse.py: -------------------------------------------------------------------------------- 1 | import tpfd 2 | 3 | #instantiate parser 4 | p = tpfd.Parser() 5 | 6 | #set up a parse rule that searches for a number 7 | @p.on_parse('The answer is {number:d}') 8 | def two_word_test(number): 9 | print (type(number), number) 10 | 11 | #parse the string for a number 12 | def int_parse1(): 13 | p.parse('The answer is 42') 14 | 15 | 16 | if __name__ == '__main__': 17 | int_parse1() -------------------------------------------------------------------------------- /examples/interator_find.py: -------------------------------------------------------------------------------- 1 | import tpfd 2 | 3 | #instantiate parser 4 | p = tpfd.Parser() 5 | 6 | #set up find rule, in this case anything inbetweeen a > and < 7 | @p.on_find('>{}<') 8 | def two_word_test(value): 9 | print(value) 10 | 11 | #submit an iterator to the find method to get all the text in the html snippits. 12 | def iter_find(): 13 | l = ['

the bold text

', '

the italicized text

', '

the underlined text

'] 14 | p.find(l) 15 | 16 | if __name__ == '__main__': 17 | iter_find() 18 | -------------------------------------------------------------------------------- /examples/interator_parse.py: -------------------------------------------------------------------------------- 1 | import tpfd 2 | 3 | #instantiate parser 4 | p = tpfd.Parser() 5 | 6 | #look for a number type, if less than 5 print 7 | @p.on_parse('{number:d}') 8 | def two_word_test(number): 9 | if number < 5: 10 | print(number) 11 | 12 | #provide generator to parse method to print out numbers 13 | def iter_parse(): 14 | p.parse(range(0,10)) 15 | 16 | if __name__ == '__main__': 17 | iter_parse() -------------------------------------------------------------------------------- /examples/multiple_words.py: -------------------------------------------------------------------------------- 1 | import tpfd 2 | 3 | #instantiate parser 4 | p = tpfd.Parser() 5 | 6 | #Set up a rule using a regular expression classifer 7 | @p.on_parse('Bring {words:^} hand grenade.') 8 | def multi_word_test(words): 9 | print (words) 10 | 11 | #Test weird string statement 12 | def word_parse(): 13 | p.parse('Bring out the holy hand grenade.') 14 | 15 | if __name__ == '__main__': 16 | word_parse() -------------------------------------------------------------------------------- /examples/unicode.py: -------------------------------------------------------------------------------- 1 | import tpfd 2 | 3 | #instantiate parser 4 | p = tpfd.Parser() 5 | 6 | #set up a 2 word capture rule 7 | @p.on_parse('The {noun} who say {thing}!') 8 | def two_word_test(noun, thing): 9 | print (noun, thing) 10 | 11 | #parse with two emoji 12 | def utf_parse1(): 13 | p.parse('The ???? who say ??!') 14 | 15 | #parse with one emoji 16 | def utf_parse2(): 17 | p.parse('The ?? who say chipmunk!') 18 | 19 | #parse with 2 emoji 20 | def utf_parse3(): 21 | p.parse('The ? who say ??!') 22 | 23 | if __name__ == '__main__': 24 | utf_parse1() 25 | utf_parse2() 26 | utf_parse3() -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | parse==1.6.6 2 | colorama==0.3.7 3 | py==1.4.31 4 | pytest==3.0.2 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import re 2 | from setuptools import setup 3 | from codecs import open 4 | 5 | 6 | #get version number from package init 7 | with open('tpfd/__init__.py', 'r') as fd: 8 | version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', 9 | fd.read(), re.MULTILINE).group(1) 10 | 11 | #get licence from package init 12 | with open('tpfd/__init__.py', 'r') as fd: 13 | license = re.search(r'^__license__\s*=\s*[\'"]([^\'"]*)[\'"]', 14 | fd.read(), re.MULTILINE).group(1) 15 | 16 | #get readme 17 | with open('README.rst', 'r', 'utf-8') as f: 18 | readme = f.read() 19 | 20 | CLASSIFIERS = ( 21 | 'Intended Audience :: Developers', 22 | 'Natural Language :: English', 23 | 'Programming Language :: Python', 24 | 'Programming Language :: Python :: 2.6', 25 | 'Programming Language :: Python :: 2.7', 26 | 'Programming Language :: Python :: 3.3', 27 | 'Programming Language :: Python :: 3.4', 28 | 'Programming Language :: Python :: 3.5', 29 | 'Programming Language :: Python :: Implementation :: PyPy') 30 | 31 | 32 | setup(name='tpfd', 33 | version=version, 34 | description='Text Parsing Function Dispatcher', 35 | long_description=readme, 36 | url='https://github.com/erinxocon/tpfd', 37 | author="Erin O'Connell", 38 | author_email='erinocon5@gmail.com', 39 | license=license, 40 | packages=['tpfd'], 41 | zip_safe=False, 42 | classifiers=CLASSIFIERS, 43 | install_requires=['parse'], 44 | data_files=['test/Test1.txt']) 45 | -------------------------------------------------------------------------------- /test/Test1.txt: -------------------------------------------------------------------------------- 1 | Bears beats Battlestar galactica 2 | Sloths are cool 3 | Mosquitos are dumb -------------------------------------------------------------------------------- /test/test_parse.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | 3 | import pytest 4 | import tpfd 5 | import logging 6 | 7 | logging.basicConfig(level=logging.DEBUG) 8 | logging.debug('test') 9 | 10 | p = tpfd.Parser() 11 | p.debug = False 12 | 13 | PARSE_LIST_RESULTS = [] 14 | 15 | FIND_LIST_RESULTS = [] 16 | 17 | FILE_RESULTS = [] 18 | 19 | 20 | @p.on_parse('{Animal} are cool') 21 | def main(animal): 22 | return animal 23 | 24 | @p.on_parse('List test {number}') 25 | def list_test(number): 26 | PARSE_LIST_RESULTS.append(True) 27 | 28 | 29 | @p.on_parse('{Animal} beats Battlestar galactica') 30 | def file_test(animal): 31 | FILE_RESULTS.append(True) 32 | 33 | 34 | @p.on_parse('The awnser is {number:d}') 35 | def int_test(number): 36 | return number 37 | 38 | 39 | @p.on_parse('The {noun} who say {thing}!') 40 | def two_word_test(noun, thing): 41 | return (noun, thing) 42 | 43 | 44 | @p.on_find('>{}<') 45 | def html_find(words): 46 | FIND_LIST_RESULTS.append(True) 47 | 48 | 49 | @p.on_find('the') 50 | def word_find(words): 51 | print (words) 52 | 53 | 54 | def test_string_parse1(): 55 | assert p.parse('Sloths are cool') == 'sloths' 56 | 57 | 58 | def test_string_parse2(): 59 | assert p.parse('Turtles are cool') == 'turtles' 60 | 61 | 62 | def test_string_parse3(): 63 | assert p.parse('Bears beats Battle Star Galactica') == None 64 | 65 | 66 | def test_string_parse4(): 67 | assert p.parse('The knights who say Ni!') == ('knights', 'ni') 68 | 69 | 70 | def test_utf_parse1(): 71 | assert p.parse('The 🇬🇧 who say ⚡️!') == ('🇬🇧', '⚡️') 72 | 73 | 74 | def test_string_find1(): 75 | p.find('The man drove the car to the store.') == 'the the the' 76 | 77 | 78 | def test_string_find3(): 79 | p.find('This string should return None') == None 80 | 81 | 82 | def test_iter_parse1(): 83 | l = ['List test 1', 'List test 2', 'List antitest 1'] 84 | p.parse(l) 85 | assert 2 == len(PARSE_LIST_RESULTS) 86 | 87 | 88 | def test_iter_find1(): 89 | l = ['

the bold text

', '

the italicized text

', 'This statement has no html tags'] 90 | p.find(l) 91 | assert 5 == len(FIND_LIST_RESULTS) 92 | 93 | 94 | def test_iter_parse_file1(): 95 | p.parse_file('Test1.txt') 96 | assert 1 == len(FILE_RESULTS) 97 | 98 | 99 | def test_int_parse1(): 100 | assert p.parse('The awnser is 42') == 42 101 | 102 | 103 | if __name__ == '__main__': 104 | pytest.main() 105 | -------------------------------------------------------------------------------- /tpfd/__init__.py: -------------------------------------------------------------------------------- 1 | __title__ = 'tpfd' 2 | __version__ = '0.2.4' 3 | __build__ = 0x02B 4 | __author__ = 'Erin O\'Connell' 5 | __license__ = 'MIT' 6 | __copyright__ = 'Copyright 2016 Erin O\'Connell' 7 | 8 | from .parser import Parser -------------------------------------------------------------------------------- /tpfd/compat.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | """ 4 | This module handles import compatibility issues between Python 2 and 5 | Python 3. 6 | """ 7 | 8 | # Syntax sugar. 9 | _ver = sys.version_info 10 | 11 | #: Python 2.x? 12 | is_py2 = (_ver[0] == 2) 13 | 14 | #: Python 3.x? 15 | is_py3 = (_ver[0] == 3) 16 | 17 | if is_py2: 18 | builtin_str = str 19 | bytes = str 20 | str = unicode 21 | basestring = basestring 22 | numeric_types = (int, long, float) 23 | 24 | elif is_py3: 25 | builtin_str = str 26 | str = str 27 | bytes = bytes 28 | basestring = (str, bytes) 29 | numeric_types = (int, float) -------------------------------------------------------------------------------- /tpfd/parser.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | 3 | """ 4 | parser.py 5 | This contains the main Parser class that can be instantiated to create rules. 6 | """ 7 | from .rules import RuleMap 8 | from .compat import basestring 9 | 10 | 11 | class Parser(object): 12 | """ 13 | Parser exposes a couple methods for reading in strings. 14 | Currently only parse_file is working. 15 | """ 16 | 17 | def __init__(self): 18 | """Initalizer""" 19 | self.debug = False 20 | self._parse_rule_map = RuleMap(list) 21 | self._find_rule_map = RuleMap(list) 22 | 23 | 24 | def on_parse(self, eventname): 25 | """ 26 | Decorator for rules. Calls the associated functions when the rule 27 | is invoked via parse found 28 | """ 29 | def parse_decorator(func): 30 | """Event decorator closure thing""" 31 | self._parse_rule_map.add_rule(eventname, func) 32 | return func 33 | return parse_decorator 34 | 35 | 36 | def on_find(self, eventname): 37 | """ 38 | Decorator for rules. Calls the associated functions when the rule 39 | is invoked via parse found 40 | """ 41 | def find_decorator(func): 42 | """Event decorator closure thing""" 43 | self._find_rule_map.add_rule(eventname, func) 44 | return func 45 | return find_decorator 46 | 47 | 48 | def parse_file(self, file): 49 | """Parses through a file""" 50 | with open(file, 'r') as f: 51 | for line in f: 52 | self._parse_rule_map.query_parse(line) 53 | 54 | 55 | def iter_parse(self, iterable): 56 | """Parses an interator/generator""" 57 | for item in iterable: 58 | self._parse_rule_map.query_parse(item) 59 | 60 | 61 | def parse_string(self, string): 62 | """Parses and int or string""" 63 | return self._parse_rule_map.query_parse(string) 64 | 65 | 66 | def parse(self, item): 67 | """Magical method that automatically chooses parse string or iter parse""" 68 | if isinstance(item, basestring): 69 | return self.parse_string(item) 70 | else: 71 | self.iter_parse(item) 72 | 73 | 74 | def find_string(self, string): 75 | """finds an int or string based on input pattern""" 76 | return self._find_rule_map.query_find(string) 77 | 78 | 79 | def iter_find(self, iterable): 80 | """Finds an string based on an input pattern and interable/generator""" 81 | for item in iterable: 82 | self._find_rule_map.query_find(item) 83 | 84 | 85 | def find_file(self, file): 86 | """find a string based on an input pattern from a file""" 87 | with open(file, 'r') as f: 88 | for line in f: 89 | self._parse_rule_map.query_parse(line) 90 | 91 | 92 | def find(self, item): 93 | """Magical method that chooses between iter_find and find_string""" 94 | if isinstance(item, basestring): 95 | return self.find_string(item) 96 | else: 97 | self.iter_find(item) 98 | -------------------------------------------------------------------------------- /tpfd/rules.py: -------------------------------------------------------------------------------- 1 | import logging 2 | #coding=utf-8 3 | 4 | from collections import defaultdict 5 | from parse import parse 6 | from parse import findall 7 | 8 | 9 | class RuleMap(defaultdict): 10 | def __init__(self, default_factory, *args, **dict): 11 | self.debug = True 12 | return super(RuleMap, self).__init__(default_factory, *args, **dict) 13 | 14 | def add_rule(self, rule, func): 15 | """Add rule to the rule map""" 16 | try: 17 | self[rule.lower()].append(func) 18 | except AttributeError: 19 | self[rule].append(func) 20 | 21 | def query_parse(self, string): 22 | """ 23 | Method that looks in the function registry 24 | to see if input text is a rule 25 | """ 26 | 27 | #set up an empty key registry 28 | key_registry = [] 29 | 30 | try: 31 | #run through all keys and try and parse against them 32 | for key in self.keys(): 33 | 34 | key_registry.append({'key': key, 'parse_resp': parse(key, str(string).lower())}) 35 | 36 | #get rid of false keys or None responses 37 | key_registry = [x for x in key_registry if x['parse_resp'] is not None] 38 | 39 | #get key from registry 40 | for i in key_registry: 41 | 42 | #qury self dictionary for matching key and function 43 | for func in self.get(i['key']): 44 | 45 | return func(**i['parse_resp'].named) 46 | 47 | except KeyError as error: 48 | logging.debug("Rule '{0} not found or matched.'".format(error)) 49 | 50 | 51 | def query_find(self, string): 52 | 53 | #set up an empty key registry 54 | key_registry = [] 55 | 56 | #run through all keys and try and parse against them 57 | for key in self.keys(): 58 | 59 | key_registry.append({'key': key, 'find_resp': findall(key, str(string).lower())}) 60 | 61 | #get rid of false keys or None responses 62 | key_registry = [x for x in key_registry if x['find_resp'] is not None] 63 | 64 | joined = '' 65 | 66 | #get key from registry and unpack it's associated response 67 | for i in key_registry: 68 | 69 | for j in i['find_resp']: 70 | 71 | joined += j[0] 72 | 73 | #qury self dictionary for matching key and function 74 | for func in self.get(i['key']): 75 | 76 | return func(joined) 77 | 78 | 79 | 80 | --------------------------------------------------------------------------------