├── tests ├── __init__.py └── demo_test.py ├── debian ├── compat ├── pycompat ├── docs ├── dirs ├── gelatin.manpages ├── watch ├── changelog ├── rules ├── copyright ├── control └── gel.1 ├── requirements.txt ├── .gitignore ├── AUTHORS ├── VERSION.in ├── docs ├── _static │ └── logo.png ├── api.rst ├── Gelatin.util.rst ├── Gelatin.compiler.rst ├── Gelatin.generator.rst ├── advanced.rst ├── index.rst ├── quick.rst ├── Makefile ├── syntax.rst └── conf.py ├── Gelatin ├── version.py ├── parser │ ├── __init__.py │ ├── Token.py │ ├── Indent.py │ ├── syntax.ebnf │ ├── Dedent.py │ ├── Newline.py │ ├── Parser.py │ └── util.py ├── compiler │ ├── __init__.py │ ├── Token.py │ ├── MatchStatement.py │ ├── Number.py │ ├── Regex.py │ ├── MatchList.py │ ├── SkipStatement.py │ ├── MatchFieldList.py │ ├── Function.py │ ├── String.py │ ├── WhenStatement.py │ ├── Grammar.py │ ├── SyntaxCompiler.py │ └── Context.py ├── __init__.py ├── generator │ ├── Json.py │ ├── __init__.py │ ├── Yaml.py │ ├── Dummy.py │ ├── Xml.py │ └── Builder.py └── util.py ├── demo ├── csv │ ├── input1.txt │ ├── output1.yaml │ ├── output1.xml │ ├── syntax.gel │ └── output1.json ├── linesplit │ ├── input1.txt │ ├── syntax.gel │ ├── output1.yaml │ ├── output1.xml │ └── output1.json ├── simple │ ├── input1.txt │ ├── output1.yaml │ ├── output1.xml │ ├── output1.json │ └── syntax.gel └── tria │ ├── input1.txt │ ├── syntax.gel │ ├── output1.yaml │ ├── output1.xml │ └── output1.json ├── .travis.yml ├── COPYING ├── version.sh ├── Makefile ├── setup.py ├── scripts └── gel └── README.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 7 2 | -------------------------------------------------------------------------------- /debian/pycompat: -------------------------------------------------------------------------------- 1 | 2 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | . 2 | -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- 1 | README 2 | TODO 3 | -------------------------------------------------------------------------------- /debian/dirs: -------------------------------------------------------------------------------- 1 | usr/bin 2 | usr/sbin 3 | -------------------------------------------------------------------------------- /debian/gelatin.manpages: -------------------------------------------------------------------------------- 1 | debian/gel.1 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | *.egg-info 3 | *.swp 4 | build 5 | dist 6 | unit_test.cfg 7 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Original author and primary maintainer: 2 | * Samuel Abels 3 | -------------------------------------------------------------------------------- /VERSION.in: -------------------------------------------------------------------------------- 1 | # Warning: This file is automatically generated. 2 | __version__ = '@VERSION@' 3 | -------------------------------------------------------------------------------- /docs/_static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/knipknap/Gelatin/HEAD/docs/_static/logo.png -------------------------------------------------------------------------------- /Gelatin/version.py: -------------------------------------------------------------------------------- 1 | # Warning: This file is automatically generated. 2 | __version__ = 'DEVELOPMENT' 3 | -------------------------------------------------------------------------------- /debian/watch: -------------------------------------------------------------------------------- 1 | version=3 2 | http://github.com/knipknap/Gelatin/downloads /knipknap/Gelatin/tarball/v(.+) 3 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | Python API 2 | ========== 3 | 4 | .. toctree:: 5 | 6 | Gelatin.util 7 | Gelatin.compiler 8 | Gelatin.generator 9 | -------------------------------------------------------------------------------- /demo/csv/input1.txt: -------------------------------------------------------------------------------- 1 | data1, data2 ,"data3'''", 'data4""',,,data5, 2 | foo1, foo2, "foo3'''", 'foo4""',,,foo5,'foo6' 3 | bar1, bar2 ,"bar3'''", 'bar4""',,,bar5, 4 | -------------------------------------------------------------------------------- /docs/Gelatin.util.rst: -------------------------------------------------------------------------------- 1 | Gelatin.util module 2 | =================== 3 | 4 | .. automodule:: Gelatin.util 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /demo/linesplit/input1.txt: -------------------------------------------------------------------------------- 1 | a nice line 2 | another & line 3 | "something else" 4 | 5 | my name is foo 6 | your name is bar 7 | 8 | yet a nice line 9 | yet another line 10 | yet something else 11 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | gelatin (0.1.12~git20100128.gb1f8fdd-0ubuntu1) lucid; urgency=low 2 | 3 | * Initial release. (LP: #514024) 4 | 5 | -- Samuel Abels Thu, 28 Jan 2010 20:15:08 +0100 6 | -------------------------------------------------------------------------------- /demo/linesplit/syntax.gel: -------------------------------------------------------------------------------- 1 | define nl /[\r\n]/ 2 | define line /[^\r\n]*/ 3 | 4 | grammar input: 5 | match line nl: 6 | out.create('line', '$0') 7 | match line: 8 | out.create('line', '$0') 9 | -------------------------------------------------------------------------------- /demo/simple/input1.txt: -------------------------------------------------------------------------------- 1 | User 2 | ---- 3 | Name: John, Lastname: Doe 4 | Office: 1st Ave 5 | Birth date: 1978-01-01 6 | 7 | User 8 | ---- 9 | Name: Jane, Lastname: Foo 10 | Office: 2nd Ave 11 | Birth date: 1970-01-01 12 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | # Uncomment this to turn on verbose mode. 4 | #export DH_VERBOSE=1 5 | DEB_PYTHON_SYSTEM=pycentral 6 | 7 | include /usr/share/cdbs/1/rules/debhelper.mk 8 | include /usr/share/cdbs/1/class/python-distutils.mk 9 | -------------------------------------------------------------------------------- /demo/linesplit/output1.yaml: -------------------------------------------------------------------------------- 1 | line: 2 | - '#text': a nice line 3 | - '#text': another & line 4 | - '#text': '"something else"' 5 | - {} 6 | - '#text': my name is foo 7 | - '#text': your name is bar 8 | - {} 9 | - '#text': yet a nice line 10 | - '#text': yet another line 11 | - '#text': yet something else 12 | -------------------------------------------------------------------------------- /demo/simple/output1.yaml: -------------------------------------------------------------------------------- 1 | user: 2 | - '@firstname': John 3 | '@lastname': Doe 4 | office: 5 | '#text': 1st Ave 6 | birth-date: 7 | '#text': '1978-01-01' 8 | - '@firstname': Jane 9 | '@lastname': Foo 10 | office: 11 | '#text': 2nd Ave 12 | birth-date: 13 | '#text': '1970-01-01' 14 | -------------------------------------------------------------------------------- /demo/simple/output1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1st Ave 4 | 1978-01-01 5 | 6 | 7 | 2nd Ave 8 | 1970-01-01 9 | 10 | 11 | -------------------------------------------------------------------------------- /demo/linesplit/output1.xml: -------------------------------------------------------------------------------- 1 | 2 | a nice line 3 | another & line 4 | "something else" 5 | 6 | my name is foo 7 | your name is bar 8 | 9 | yet a nice line 10 | yet another line 11 | yet something else 12 | 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.3" 4 | - "3.4" 5 | - "3.5" 6 | - "3.6" 7 | # command to install dependencies 8 | install: 9 | - "pip install -r requirements.txt" 10 | - "pip install coveralls" 11 | # command to run tests 12 | script: coverage run --source=Gelatin -m unittest discover -v tests "*Test.py" 13 | after_success: 14 | coveralls 15 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | This package was debianized by Samuel Abels on 2 | Fri, 22 Jan 2010 22:15:08 +0100. 3 | 4 | It was downloaded from http://wiki.github.com/knipknap/Gelatin/ 5 | 6 | Upstream Author: Samuel Abels 7 | 8 | Gelatin Copyright (C) 2010 Samuel Abels. Gelatin is distributed with no 9 | warranty under the terms of the GNU Public License. See 10 | /usr/share/common-licenses/GPL. 11 | -------------------------------------------------------------------------------- /demo/csv/output1.yaml: -------------------------------------------------------------------------------- 1 | line: 2 | - column: 3 | - '#text': data1 4 | - '#text': 'data2 ' 5 | - '#text': data3''' 6 | - '#text': data4"" 7 | - {} 8 | - {} 9 | - '#text': data5 10 | - {} 11 | - column: 12 | - '#text': foo1 13 | - '#text': foo2 14 | - '#text': foo3''' 15 | - '#text': foo4"" 16 | - {} 17 | - {} 18 | - '#text': foo5 19 | - '#text': foo6 20 | - column: 21 | - '#text': bar1 22 | - '#text': 'bar2 ' 23 | - '#text': bar3''' 24 | - '#text': bar4"" 25 | - {} 26 | - {} 27 | - '#text': bar5 28 | - {} 29 | -------------------------------------------------------------------------------- /demo/simple/output1.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": [ 3 | { 4 | "@firstname": "John", 5 | "@lastname": "Doe", 6 | "office": { 7 | "#text": "1st Ave" 8 | }, 9 | "birth-date": { 10 | "#text": "1978-01-01" 11 | } 12 | }, 13 | { 14 | "@firstname": "Jane", 15 | "@lastname": "Foo", 16 | "office": { 17 | "#text": "2nd Ave" 18 | }, 19 | "birth-date": { 20 | "#text": "1970-01-01" 21 | } 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /docs/Gelatin.compiler.rst: -------------------------------------------------------------------------------- 1 | Gelatin.compiler package 2 | ======================== 3 | 4 | Module contents 5 | --------------- 6 | 7 | .. automodule:: Gelatin.compiler 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | Gelatin.compiler.Context module 13 | ------------------------------- 14 | 15 | .. automodule:: Gelatin.compiler.Context 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | Gelatin.compiler.SyntaxCompiler module 21 | -------------------------------------- 22 | 23 | .. automodule:: Gelatin.compiler.SyntaxCompiler 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | -------------------------------------------------------------------------------- /demo/linesplit/output1.json: -------------------------------------------------------------------------------- 1 | { 2 | "line": [ 3 | { 4 | "#text": "a nice line" 5 | }, 6 | { 7 | "#text": "another & line" 8 | }, 9 | { 10 | "#text": "\"something else\"" 11 | }, 12 | {}, 13 | { 14 | "#text": "my name is foo" 15 | }, 16 | { 17 | "#text": "your name is bar" 18 | }, 19 | {}, 20 | { 21 | "#text": "yet a nice line" 22 | }, 23 | { 24 | "#text": "yet another line" 25 | }, 26 | { 27 | "#text": "yet something else" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: gelatin 2 | Section: contrib/text 3 | Priority: optional 4 | Maintainer: Ubuntu Developers 5 | XSBC-Original-Maintainer: Samuel Abels 6 | Build-Depends: debhelper (>= 7), python, python-central (>= 0.6.0), cdbs (>= 0.4.49) 7 | XS-Python-Version: >= 2.6 8 | Standards-Version: 3.8.3 9 | Homepage: http://wiki.github.com/knipknap/Gelatin/ 10 | 11 | Package: gelatin 12 | Architecture: all 13 | Depends: ${python:Depends}, python-lxml, python-yaml, ${misc:Depends} 14 | XB-Python-Version: ${python:Versions} 15 | Description: Transform text files to XML, JSON, or YAML 16 | Gelatin is a parser generator for converting text to a structured 17 | format such as XML, JSON or YAML. 18 | -------------------------------------------------------------------------------- /demo/csv/output1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | data1 4 | data2 5 | data3''' 6 | data4"" 7 | 8 | 9 | data5 10 | 11 | 12 | 13 | foo1 14 | foo2 15 | foo3''' 16 | foo4"" 17 | 18 | 19 | foo5 20 | foo6 21 | 22 | 23 | bar1 24 | bar2 25 | bar3''' 26 | bar4"" 27 | 28 | 29 | bar5 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /demo/simple/syntax.gel: -------------------------------------------------------------------------------- 1 | # Define commonly used data types. This is optional, but 2 | # makes your life a litte easier by allowing to reuse regular 3 | # expressions in the grammar. 4 | define nl /[\r\n]/ 5 | define ws /\s+/ 6 | define fieldname /[\w ]+/ 7 | define value /[^\r\n,]+/ 8 | define field_end /[\r\n,] */ 9 | 10 | grammar user: 11 | match 'Name:' ws value field_end: 12 | out.add_attribute('.', 'firstname', '$2') 13 | match 'Lastname:' ws value field_end: 14 | out.add_attribute('.', 'lastname', '$2') 15 | match fieldname ':' ws value field_end: 16 | out.add('$0', '$3') 17 | match nl: 18 | do.return() 19 | 20 | # The grammar named "input" is the entry point for the converter. 21 | grammar input: 22 | match 'User' nl '----' nl: 23 | out.open('user') 24 | user() 25 | -------------------------------------------------------------------------------- /debian/gel.1: -------------------------------------------------------------------------------- 1 | .TH GELATIN 1 "January 22, 2010" 2 | .SH NAME 3 | gel \- Transform text files to XML, JSON, or YAML 4 | .SH SYNOPSIS 5 | .B gel 6 | .RI [ options ] " file" 7 | .br 8 | .SH DESCRIPTION 9 | Gelatin is a fast and simple way for converting text to a structured 10 | format such as XML, JSON, or YAML. 11 | .SH OPTIONS 12 | .B \-h, \-\-help 13 | Show summary of options. 14 | .TP 15 | .B \-f FORMAT, \-\-format FORMAT 16 | The output format. Allowed values are xml, json, yaml, or none. 17 | .TP 18 | .B \-s FILE, \-\-syntax FILE 19 | The file containing the syntax for parsing the input. 20 | .TP 21 | .B \-v, \-\-version 22 | Show version of program. 23 | .SH AUTHOR 24 | Gelatin was written by Samuel Abels . 25 | .PP 26 | This manual page was written by Samuel Abels , 27 | for the Debian project (and may be used by others). 28 | -------------------------------------------------------------------------------- /demo/csv/syntax.gel: -------------------------------------------------------------------------------- 1 | define nl /[\r\n]/ 2 | define fs /[ \t]*,[ \t]*/ 3 | define quote1 /"/ 4 | define value1 /[^\r\n"]*/ 5 | define quote2 /'/ 6 | define value2 /[^\r\n']*/ 7 | define value3 /[^\r\n,]*/ 8 | 9 | grammar line: 10 | match quote1 value1 quote1 fs: 11 | out.create('column', '$1') 12 | match quote1 value1 quote1 nl: 13 | out.create('column', '$1') 14 | do.return() 15 | match quote2 value2 quote2 fs: 16 | out.create('column', '$1') 17 | match quote2 value2 quote2 nl: 18 | out.create('column', '$1') 19 | do.return() 20 | match value3 fs: 21 | out.create('column', '$0') 22 | match value3 nl: 23 | out.create('column', '$0') 24 | do.return() 25 | 26 | # The grammar named "input" is the entry point for the converter. 27 | grammar input: 28 | match / */: 29 | out.open('line') 30 | line() 31 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Samuel Abels 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Gelatin/parser/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from .Parser import Parser 21 | -------------------------------------------------------------------------------- /docs/Gelatin.generator.rst: -------------------------------------------------------------------------------- 1 | Gelatin.generator package 2 | ========================= 3 | 4 | Module contents 5 | --------------- 6 | 7 | .. automodule:: Gelatin.generator 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | Gelatin.generator.Builder module 13 | -------------------------------- 14 | 15 | .. automodule:: Gelatin.generator.Builder 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | Gelatin.generator.Dummy module 21 | ------------------------------ 22 | 23 | .. automodule:: Gelatin.generator.Dummy 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | Gelatin.generator.Json module 29 | ----------------------------- 30 | 31 | .. automodule:: Gelatin.generator.Json 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | Gelatin.generator.Xml module 37 | ---------------------------- 38 | 39 | .. automodule:: Gelatin.generator.Xml 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | Gelatin.generator.Yaml module 45 | ----------------------------- 46 | 47 | .. automodule:: Gelatin.generator.Yaml 48 | :members: 49 | :undoc-members: 50 | :show-inheritance: 51 | -------------------------------------------------------------------------------- /Gelatin/compiler/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from .SyntaxCompiler import SyntaxCompiler 21 | -------------------------------------------------------------------------------- /Gelatin/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | """ 21 | The core module. 22 | """ 23 | from .version import __version__ 24 | 25 | INDENT_WIDTH = 4 26 | INDENT = ' ' * INDENT_WIDTH 27 | SEARCH_WINDOW = 1000 28 | -------------------------------------------------------------------------------- /Gelatin/compiler/Token.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from Gelatin import INDENT 21 | 22 | 23 | class Token(object): 24 | 25 | def dump(self, indent=0): 26 | return INDENT * indent, self.__class__.__name__ 27 | -------------------------------------------------------------------------------- /Gelatin/generator/Json.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | import json 21 | from pprint import PrettyPrinter 22 | 23 | class Json(object): 24 | 25 | def serialize_doc(self, node): 26 | return json.dumps(node.to_dict(), indent=4, ensure_ascii=False) 27 | -------------------------------------------------------------------------------- /Gelatin/compiler/MatchStatement.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from .WhenStatement import WhenStatement 21 | 22 | 23 | class MatchStatement(WhenStatement): 24 | 25 | def parse(self, context, debug=0): 26 | match = self.matchlist.match(context) 27 | return self._handle_match(context, match, debug) 28 | -------------------------------------------------------------------------------- /docs/advanced.rst: -------------------------------------------------------------------------------- 1 | Common Operations 2 | ================= 3 | 4 | Generating XML attributes 5 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 6 | 7 | There are two ways for creating an attribute. The first is using URL 8 | notation within a node name: 9 | 10 | :: 11 | 12 | grammar input: 13 | match 'User' nl '----' nl 'Name:' ws value field_end: 14 | out.enter('user?name="$6"') 15 | user() 16 | 17 | The second, equivalent way calls add\_attribute() explicitely: 18 | 19 | :: 20 | 21 | grammar input: 22 | match 'User' nl '----' nl 'Name:' ws value field_end: 23 | out.enter('user') 24 | out.add_attribute('.', 'name', '$6') 25 | user() 26 | 27 | Skipping Values 28 | ~~~~~~~~~~~~~~~ 29 | 30 | :: 31 | 32 | match /# .*[\r\n]/: 33 | do.skip() 34 | 35 | Matching Multiple Values 36 | ~~~~~~~~~~~~~~~~~~~~~~~~ 37 | 38 | :: 39 | 40 | match /# .*[\r\n]/ 41 | | '/*' /[^\r\n]/ '*/' nl: 42 | do.skip() 43 | 44 | Grammar Inheritance 45 | ~~~~~~~~~~~~~~~~~~~ 46 | 47 | A grammar that uses inheritance executes the inherited match statements 48 | before trying it's own:: 49 | 50 | grammar default: 51 | match nl: 52 | do.return() 53 | match ws: 54 | do.next() 55 | 56 | grammar user(default): 57 | match fieldname ':' ws value field_end: 58 | out.add('$0', '$3') 59 | 60 | In this case, the *user* grammar inherits the whitespace rules from the 61 | *default* grammar. 62 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. image:: _static/logo.png 2 | :target: http://gelatin.readthedocs.io 3 | 4 | .. image:: https://travis-ci.org/knipknap/Gelatin.svg?branch=master 5 | :target: https://travis-ci.org/knipknap/Gelatin 6 | 7 | .. image:: https://coveralls.io/repos/github/knipknap/Gelatin/badge.svg?branch=master 8 | :target: https://coveralls.io/github/knipknap/Gelatin?branch=master 9 | 10 | .. image:: https://lima.codeclimate.com/github/knipknap/Gelatin/badges/gpa.svg 11 | :target: https://lima.codeclimate.com/github/knipknap/Gelatin 12 | :alt: Code Climate 13 | 14 | .. image:: https://img.shields.io/github/stars/knipknap/Gelatin.svg 15 | :target: https://github.com/knipknap/Gelatin/stargazers 16 | 17 | .. image:: https://img.shields.io/github/license/knipknap/Gelatin.svg 18 | :target: https://github.com/knipknap/Gelatin/blob/master/COPYING 19 | 20 | | 21 | What is Gelatin? 22 | ================ 23 | 24 | Gelatin turns your text soup into something solid. It is a combined 25 | lexer, parser, and output generator. It converts text files to XML, JSON, 26 | or YAML. It is a simple language for converting text into a structured 27 | formats. 28 | 29 | Development 30 | ----------- 31 | 32 | Gelatin is on `GitHub `_. 33 | 34 | License 35 | ------- 36 | Gelatin is published under the `MIT licence `_. 37 | 38 | Contents 39 | -------- 40 | 41 | .. toctree:: 42 | :maxdepth: 2 43 | 44 | quick 45 | advanced 46 | syntax 47 | api 48 | -------------------------------------------------------------------------------- /Gelatin/compiler/Number.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from Gelatin import INDENT 21 | from .Token import Token 22 | 23 | 24 | class Number(Token): 25 | 26 | def __init__(self, number): 27 | self.data = number 28 | 29 | def value(self): 30 | return self.data 31 | 32 | def re_value(self): 33 | return str(self.data) 34 | 35 | def dump(self, indent=0): 36 | return INDENT * indent + str(self.data) 37 | -------------------------------------------------------------------------------- /demo/tria/input1.txt: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------- 2 | Woche: 1 3 | Mo04Jan10 4 | Di05Jan10 5 | Mi06Jan10 6 | Do07Jan10 Run 7 | Strecke: Unterführung 8 | Kommentar: LS3AsicsTrabuco, 5mm Neuschnee, rutschig 9 | Zeit: 15:34(15:34),31:20(15.45), 10 | Swim 11 | Ort: Hallenbad Markgröningen 12 | Laenge: 400 ES 13 | Laenge: 100 AS 14 | 15 | Fr08Jan10 16 | Sa09Jan10 Swim 17 | Kommentar: 6. Einheit, Plan Dez09, Rückentechnik 18 | Ort: Hallenbad Markgröningen 19 | Länge: 400 ES 20 | Laenge: 6*50 Technik 21 | Laenge: 8*50 GA1(25 Kraul, 25 Rücken) 22 | Laenge: 6*100 GA1(Kraul + Rücken + Kraul + Brust im Wechsel) 23 | Laenge: 25 tauchen, 25 tauchen, 29 tauchen, 33 tauchen 24 | Laenge: 100 AS 25 | 26 | So10Jan10 27 | #------------------------------------------------------------------------------- 28 | Woche: 2 29 | Mo11Jan10 30 | Mi13Jan10 31 | Do14Jan10 Bike 32 | Kommentar: Feldwege + Straße 33 | Material: Centurion 34 | Strecke: Feldwege nach Eglosheim, Monrepos, Bietigheim, Bissingen, dann 35 | Strecke: Straße nach Untermberg, Sachsenheim (Industriegebiet), Fontanis-Lager, 36 | Strecke: Kreissel Oberriexingen, Unterriexingen. 37 | Laenge: 44.2 km 38 | Schnitt: 18.3 km/h 39 | 40 | Fr15Jan10 41 | 42 | So17Jan10 43 | #------------------------------------------------------------------------------- 44 | -------------------------------------------------------------------------------- /Gelatin/generator/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from .Builder import Builder 21 | from .Dummy import Dummy 22 | from .Xml import Xml 23 | from .Json import Json 24 | from .Yaml import Yaml 25 | 26 | generator_map = {'none': Dummy, 27 | 'xml': Xml, 28 | 'json': Json, 29 | 'yaml': Yaml} 30 | 31 | 32 | def new(format): 33 | cls = generator_map.get(format) 34 | if not cls: 35 | return None 36 | return cls() 37 | -------------------------------------------------------------------------------- /version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Tag revisions like this: 3 | # $ git tag -a -m "v0.2" v0.2 4 | VERSION_IN=VERSION.in 5 | VERSION_FILE=Gelatin/version.py 6 | 7 | # Check that we are actually in a git managed project. 8 | if [ ! -e .git -a -z "$1" ]; then 9 | echo >&2 Not a git repository. 10 | exit 1 11 | fi 12 | 13 | # Make sure that we have permission to modify the version file. 14 | if [ -r $VERSION_FILE -a ! -w $VERSION_FILE ]; then 15 | echo >&2 No permission to modify $VERSION_FILE. 16 | exit 1 17 | fi 18 | 19 | # By default, get the version number from "git describe". 20 | if [ ! -z "$1" ]; then 21 | VERSION=$1 22 | else 23 | HEAD=`git log -1 --pretty=format:%H HEAD` 24 | VERSION=`git describe $HEAD --tags --match "v[0-9]*" | sed 's/^v//;s/-[^\-]*$//;s/-/./' 2>/dev/null` 25 | if [ -z "$VERSION" ]; then 26 | echo >&2 No matching tag was found. 27 | exit 1 28 | fi 29 | fi 30 | 31 | # If the --reset switch was given, reset the version number to 'DEVELOPMENT'. 32 | [ "$1" = "--reset" ] && VERSION='DEVELOPMENT' 33 | 34 | # If there is no version file, we are already done. 35 | echo Version is $VERSION 36 | [ ! -r $VERSION_FILE ] && exit 0 37 | 38 | # Check whether the version file already contains this number, 39 | # and only touch it if there is a change to avoid changing 40 | # the timestamp. 41 | VERSION_FILE_TMP=`mktemp` 42 | cat $VERSION_IN | sed "s/@VERSION@/$VERSION/g" > $VERSION_FILE_TMP 43 | if diff -q $VERSION_FILE_TMP $VERSION_FILE; then 44 | echo Version file unchanged. 45 | rm $VERSION_FILE_TMP 46 | exit 0 47 | fi 48 | 49 | mv $VERSION_FILE_TMP $VERSION_FILE 50 | echo Version file updated. 51 | -------------------------------------------------------------------------------- /Gelatin/parser/Token.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from simpleparse.objectgenerator import Prebuilt 21 | from simpleparse.stt.TextTools import Call 22 | 23 | 24 | class Token(object): 25 | 26 | def __init__(self, processor): 27 | self.processor = processor 28 | 29 | def __call__(self, buffer, start, end): 30 | raise NotImplementedError('Token is abstract') 31 | 32 | def table(self): 33 | table = (None, Call, self), 34 | return Prebuilt(value=table, report=False) 35 | -------------------------------------------------------------------------------- /Gelatin/parser/Indent.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from .util import eat_indent, count_indent, error 21 | from .Token import Token 22 | 23 | 24 | class Indent(Token): 25 | 26 | def __call__(self, buffer, start, end): 27 | after_indent = eat_indent(buffer, start, end) 28 | new_indent = count_indent(buffer, after_indent) 29 | if new_indent != self.processor.indent + 1: 30 | error(buffer, start, 'Indentation error') 31 | self.processor.indent = new_indent 32 | return after_indent 33 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME=Gelatin 2 | VERSION=`python3 setup.py --version | sed s/^v//` 3 | 4 | ################################################################### 5 | # Standard targets. 6 | ################################################################### 7 | .PHONY : clean 8 | clean: 9 | find . -name "*.pyc" -o -name "*.pyo" | xargs -n1 rm -f 10 | rm -Rf build 11 | 12 | .PHONY : dist-clean 13 | dist-clean: clean 14 | rm -Rf dist src/*.egg-info 15 | 16 | .PHONY : doc 17 | doc: 18 | cd doc; make 19 | 20 | uninstall: 21 | # Sorry, Python's distutils support no such action yet. 22 | 23 | .PHONY : tests 24 | tests: 25 | python3 setup.py test 26 | 27 | ################################################################### 28 | # Package builders. 29 | ################################################################### 30 | targz: 31 | ./version.sh 32 | python3 setup.py sdist --formats gztar 33 | ./version.sh --reset 34 | 35 | tarbz: 36 | ./version.sh 37 | python3 setup.py sdist --formats bztar 38 | ./version.sh --reset 39 | 40 | wheel: 41 | ./version.sh 42 | python3 setup.py bdist_wheel 43 | ./version.sh --reset 44 | 45 | deb: 46 | ./version.sh 47 | DEBVERSION=`head -1 debian/changelog | sed 's/^\(.*\) (\(.*\)-0ubuntu1).*/\1_\2/'`; \ 48 | git archive HEAD | gzip >../$$DEBVERSION.orig.tar.gz 49 | debuild -S -sa -tc -i -I 50 | ./version.sh --reset 51 | 52 | dist: targz tarbz wheel 53 | 54 | ################################################################### 55 | # Publishers. 56 | ################################################################### 57 | dist-publish: 58 | ./version.sh 59 | python3 setup.py bdist_wheel --universal upload 60 | ./version.sh --reset 61 | -------------------------------------------------------------------------------- /Gelatin/compiler/Regex.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | try: 21 | import re2 as re 22 | except ImportError: 23 | import re 24 | from Gelatin import INDENT 25 | from .Token import Token 26 | 27 | 28 | class Regex(Token): 29 | data = None 30 | re_obj = None 31 | 32 | def re_value(self): 33 | return self.data 34 | 35 | def value(self): 36 | if not self.re_obj: 37 | self.re_obj = re.compile(self.data) 38 | return self.re_obj 39 | 40 | def dump(self, indent=0): 41 | return INDENT * indent + '/' + self.data + '/' 42 | -------------------------------------------------------------------------------- /Gelatin/generator/Yaml.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | import yaml 21 | from collections import OrderedDict 22 | 23 | def represent_ordereddict(dumper, data): 24 | value = [] 25 | for item_key, item_value in data.items(): 26 | node_key = dumper.represent_data(item_key) 27 | node_value = dumper.represent_data(item_value) 28 | value.append((node_key, node_value)) 29 | return yaml.nodes.MappingNode(u'tag:yaml.org,2002:map', value) 30 | 31 | yaml.add_representer(OrderedDict, represent_ordereddict) 32 | 33 | 34 | class Yaml(object): 35 | 36 | def serialize_doc(self, node): 37 | return yaml.dump(node.to_dict()) 38 | -------------------------------------------------------------------------------- /Gelatin/generator/Dummy.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from .Builder import Builder 21 | 22 | 23 | class Dummy(Builder): 24 | 25 | def __init__(self): 26 | pass 27 | 28 | def serialize(self): 29 | return '' 30 | 31 | def set_root_name(self, name): 32 | pass 33 | 34 | def dump(self): 35 | print(self.serialize()) 36 | 37 | def add(self, path, data=None, replace=False): 38 | pass 39 | 40 | def add_attribute(self, path, name, value): 41 | pass 42 | 43 | def open(self, path): 44 | pass 45 | 46 | def enter(self, path): 47 | pass 48 | 49 | def leave(self): 50 | pass 51 | -------------------------------------------------------------------------------- /Gelatin/parser/syntax.ebnf: -------------------------------------------------------------------------------- 1 | # Common tokens. 2 | := [ \t\n]+ 3 | number := [0-9]+ 4 | := [a-zA-Z0-9_]+ 5 | varname := word 6 | 7 | # Comments. 8 | := "#", -NEWLINE*, NEWLINE 9 | 10 | # Strings. 11 | := -"'" 12 | := "\\'" 13 | string_data := (string_esc_char / string_char)+ 14 | string := "'", string_data, "'" 15 | 16 | # Regular expressions. 17 | := -"/" 18 | := "\\/" 19 | re_data := (re_esc_char / re_char)+ 20 | regex := "/", re_data, "/" 21 | 22 | # Function calls. 23 | func_name := word, (".", word)? 24 | arg_list := expression, (",", ws, expression)* 25 | := "(", arg_list?, ")" 26 | function := func_name, func_args 27 | 28 | # Expressions. 29 | >expression< := string / regex / number / varname 30 | 31 | # "match" and "when" statements. 32 | match_field_list := expression, (ws, expression)* 33 | match_list := match_field_list, (ws, "|", ws, match_field_list)* 34 | match_stmt := "match", ws, match_list, ':', suite 35 | imatch_stmt := "imatch", ws, match_list, ':', suite 36 | when_stmt := "when", ws, match_list, ':', suite 37 | 38 | # "skip" statement. 39 | skip_stmt := "skip", ws, expression, NEWLINE 40 | 41 | # "define" statement. 42 | define_stmt := "define", ws, varname, ws, expression, NEWLINE 43 | 44 | # "grammar" statement. 45 | >simple_stmt< := comment / function 46 | >compound_stmt< := match_stmt / imatch_stmt / when_stmt / skip_stmt 47 | >stmt< := ((simple_stmt, NEWLINE) / compound_stmt)+ 48 | inherit := '(', varname, ')' 49 | suite := NEWLINE, INDENT, stmt+, DEDENT 50 | grammar_stmt := "grammar", ws, varname, inherit?, ":", suite 51 | 52 | root := (comment / define_stmt / grammar_stmt / NEWLINE)+ 53 | -------------------------------------------------------------------------------- /Gelatin/parser/Dedent.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from simpleparse.objectgenerator import Prebuilt 21 | from simpleparse.stt.TextTools import Call, Skip 22 | from .util import eat_indent, count_indent 23 | from .Token import Token 24 | 25 | 26 | class Dedent(Token): 27 | 28 | def __call__(self, buffer, start, end): 29 | if start > end: 30 | return start + 1 31 | after_indent = eat_indent(buffer, start, end) 32 | self.processor.indent = count_indent(buffer, after_indent) 33 | return after_indent + 1 # +1/-1 hack 34 | 35 | def table(self): 36 | table = (None, Call, self), (None, Skip, -1) # +1/-1 hack 37 | return Prebuilt(value=table, report=False) 38 | -------------------------------------------------------------------------------- /demo/csv/output1.json: -------------------------------------------------------------------------------- 1 | { 2 | "line": [ 3 | { 4 | "column": [ 5 | { 6 | "#text": "data1" 7 | }, 8 | { 9 | "#text": "data2 " 10 | }, 11 | { 12 | "#text": "data3'''" 13 | }, 14 | { 15 | "#text": "data4\"\"" 16 | }, 17 | {}, 18 | {}, 19 | { 20 | "#text": "data5" 21 | }, 22 | {} 23 | ] 24 | }, 25 | { 26 | "column": [ 27 | { 28 | "#text": "foo1" 29 | }, 30 | { 31 | "#text": "foo2" 32 | }, 33 | { 34 | "#text": "foo3'''" 35 | }, 36 | { 37 | "#text": "foo4\"\"" 38 | }, 39 | {}, 40 | {}, 41 | { 42 | "#text": "foo5" 43 | }, 44 | { 45 | "#text": "foo6" 46 | } 47 | ] 48 | }, 49 | { 50 | "column": [ 51 | { 52 | "#text": "bar1" 53 | }, 54 | { 55 | "#text": "bar2 " 56 | }, 57 | { 58 | "#text": "bar3'''" 59 | }, 60 | { 61 | "#text": "bar4\"\"" 62 | }, 63 | {}, 64 | {}, 65 | { 66 | "#text": "bar5" 67 | }, 68 | {} 69 | ] 70 | } 71 | ] 72 | } -------------------------------------------------------------------------------- /demo/tria/syntax.gel: -------------------------------------------------------------------------------- 1 | # Gelatin Grammar for Triathlon. 2 | define nl /[\r\n]+/ 3 | define nonl /[^\r\n]/ 4 | define ws /\s+/ 5 | define fieldname /[\w ]+/ 6 | define fieldvalue /[^\r\n]+/ 7 | define date /[0-9][0-9]...[0-9][0-9]/ 8 | define field_end /[\r\n,] */ 9 | define comment /#[^\r\n]*[\r\n]+/ 10 | define weekday /Mo|Di|Mi|Do|Fr|Sa|So/ 11 | define discipline /Swim|Bike|Run/ 12 | define integer /[0-9]+/ 13 | 14 | grammar unit: 15 | match ws 'Strecke:' ws fieldvalue nl: 16 | out.add('route', '$3') 17 | match ws 'Länge:' ws fieldvalue nl 18 | | ws 'Laenge:' ws fieldvalue nl: 19 | out.open('distance') 20 | out.add('.', '$3') 21 | match ws 'Zeit:' ws fieldvalue nl: 22 | out.add('time', '$3') 23 | match ws 'Kommentar:' ws fieldvalue nl: 24 | out.add('comment', '$3') 25 | match ws 'Ort:' ws fieldvalue nl: 26 | out.add('place', '$3') 27 | match ws 'Schnitt:' ws fieldvalue nl: 28 | out.add('average', '$3') 29 | match ws 'Material:' ws fieldvalue nl: 30 | out.add('material', '$3') 31 | match nl: 32 | do.return() 33 | do.return() 34 | 35 | grammar day: 36 | match ws discipline nl: 37 | out.open('unit') 38 | out.add('discipline', '$1') 39 | unit() 40 | do.return() 41 | match nl: 42 | do.return() 43 | do.return() 44 | 45 | grammar week: 46 | match weekday date: 47 | out.open('day') 48 | out.add('weekday', '$0') 49 | out.add('date', '$1') 50 | day() 51 | match comment nl: 52 | do.return() 53 | 54 | # The grammar named "input" is the entry point for the converter. 55 | grammar input: 56 | out.set_root_name('tria') 57 | skip comment 58 | match 'Woche:' ws integer nl: 59 | out.open('week') 60 | out.add_attribute('.', 'number', '$2') 61 | week() 62 | -------------------------------------------------------------------------------- /Gelatin/compiler/MatchList.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from Gelatin import INDENT 21 | from .Token import Token 22 | 23 | 24 | class MatchList(Token): 25 | 26 | def __init__(self): 27 | self.field_lists = [] 28 | 29 | def when(self, context): 30 | for field_list in self.field_lists: 31 | match = field_list.when(context) 32 | if match: 33 | return match 34 | return None 35 | 36 | def match(self, context): 37 | for field_list in self.field_lists: 38 | match = field_list.match(context) 39 | if match: 40 | return match 41 | return None 42 | 43 | def dump(self, indent=0): 44 | res = '' 45 | for field_list in self.field_lists: 46 | res += field_list.dump(indent) + '\n' 47 | return res.rstrip() + ':\n' 48 | -------------------------------------------------------------------------------- /Gelatin/compiler/SkipStatement.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from __future__ import print_function 21 | import re 22 | from Gelatin import INDENT, SEARCH_WINDOW 23 | from .Token import Token 24 | 25 | 26 | class SkipStatement(Token): 27 | 28 | def __init__(self): 29 | self.match = None 30 | self.regex = None 31 | 32 | def parse(self, context, debug=0): 33 | if not self.regex: 34 | self.regex = re.compile(self.match.re_value()) 35 | 36 | end = context.start+SEARCH_WINDOW 37 | match = self.regex.match(context.input[context.start:end]) 38 | if match is not None: 39 | start, end = match.span(0) 40 | context.start += end - start 41 | return 1 42 | return 0 43 | 44 | def dump(self, indent=0): 45 | res = INDENT * indent + 'match:\n' 46 | return res + self.matchlist.dump(indent + 1) 47 | -------------------------------------------------------------------------------- /Gelatin/generator/Xml.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from __future__ import absolute_import 21 | import sys 22 | from lxml import etree 23 | from .Builder import Builder 24 | 25 | class Xml(object): 26 | 27 | def serialize_node(self, node): 28 | elem = etree.Element(node.name, **dict(node.attribs)) 29 | elem.text = node.text 30 | for child_list in node.children.values(): 31 | for child in child_list: 32 | subelem = self.serialize_node(child) 33 | elem.append(subelem) 34 | return elem 35 | 36 | def serialize_doc(self, node): 37 | doc = etree.Element(node.name if node.name else 'xml') 38 | for child_list in node.children.values(): 39 | for child in child_list: 40 | subelem = self.serialize_node(child) 41 | doc.append(subelem) 42 | return etree.tostring(doc, encoding='unicode', pretty_print=True) 43 | -------------------------------------------------------------------------------- /Gelatin/parser/Newline.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from simpleparse.objectgenerator import Prebuilt 21 | from simpleparse.stt.TextTools import Call, Skip 22 | from .util import eat_indent 23 | from .Token import Token 24 | 25 | 26 | class Newline(Token): 27 | 28 | def __call__(self, buffer, start, end): 29 | # Skip empty lines. 30 | thestart = start 31 | try: 32 | if buffer[thestart] != '\n': 33 | return thestart 34 | while buffer[thestart] == '\n': 35 | thestart += 1 36 | except IndexError: 37 | return thestart + 2 # +1/-1 hack #EOF 38 | 39 | # If the indent of the non-empty line matches, we are done. 40 | return eat_indent(buffer, thestart, end, self.processor.indent) + 1 # +1/-1 hack 41 | 42 | def table(self): 43 | table = (None, Call, self), (None, Skip, -1) # +1/-1 hack 44 | return Prebuilt(value=table, report=False) 45 | -------------------------------------------------------------------------------- /Gelatin/compiler/MatchFieldList.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | try: 21 | import re2 as re 22 | except ImportError: 23 | import re 24 | from Gelatin import INDENT, SEARCH_WINDOW 25 | from .Token import Token 26 | 27 | 28 | class MatchFieldList(Token): 29 | 30 | def __init__(self, modifiers=None): 31 | self.expressions = [] 32 | self.regex = None 33 | self.modifiers = modifiers 34 | 35 | def when(self, context): 36 | if not self.regex: 37 | regex = ')('.join(e.re_value() for e in self.expressions) 38 | self.regex = re.compile('(' + regex + ')', self.modifiers) 39 | 40 | end = context.start+SEARCH_WINDOW 41 | return self.regex.match(context.input[context.start:end]) 42 | 43 | def match(self, context): 44 | match = self.when(context) 45 | if not match: 46 | return None 47 | start, end = match.span(0) 48 | context.start += end - start 49 | return match 50 | 51 | def dump(self, indent=0): 52 | res = INDENT * indent 53 | for expr in self.expressions: 54 | res += expr.dump() + ' ' 55 | return res.rstrip() 56 | -------------------------------------------------------------------------------- /Gelatin/compiler/Function.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from Gelatin import INDENT 21 | from .Token import Token 22 | 23 | 24 | class Function(Token): 25 | 26 | def __init__(self): 27 | self.name = None 28 | self.args = [] 29 | 30 | def parse(self, context, debug=0): 31 | # Function names that have NO dot in them are references to another 32 | # grammar. 33 | if '.' not in self.name: 34 | start = context.start 35 | grammar = context.grammars.get(self.name) 36 | if not grammar: 37 | raise Exception('call to undefined grammar ' + self.name) 38 | grammar.parse(context, debug) 39 | if context.start != start: 40 | return 1 41 | return 0 42 | 43 | # Other functions are utilities. 44 | func = context.functions.get(self.name) 45 | if not func: 46 | raise Exception('unknown function ' + self.name) 47 | return func(context, *[a.value() for a in self.args]) 48 | 49 | def dump(self, indent=0): 50 | args = ', '.join(a.dump() for a in self.args) 51 | return INDENT * indent + self.name + '(' + args + ')' 52 | -------------------------------------------------------------------------------- /Gelatin/compiler/String.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | import re 21 | try: 22 | from urllib.parse import quote 23 | except ImportError: # Python 2 24 | from urllib import quote 25 | from Gelatin import INDENT 26 | from .Token import Token 27 | 28 | _string_re = re.compile(r'(\\?)\$(\d*)') 29 | 30 | 31 | class String(Token): 32 | 33 | def __init__(self, context, data): 34 | self.context = context 35 | self.data = data 36 | 37 | def _expand_string(self, match): 38 | field = match.group(0) 39 | escape = match.group(1) 40 | fieldnum = match.group(2) 41 | 42 | # Check the variable name syntax. 43 | if escape: 44 | return '$' + fieldnum 45 | elif fieldnum == '': 46 | return '$' 47 | 48 | # Check the variable value. 49 | cmatch = self.context.re_stack[-1] 50 | try: 51 | return quote(cmatch.group(int(fieldnum) + 1), safe=' ') 52 | except IndexError as e: 53 | raise Exception( 54 | 'invalid field number %s in %s' % (fieldnum, self.data)) 55 | 56 | def value(self): 57 | return _string_re.sub(self._expand_string, self.data) 58 | 59 | def re_value(self): 60 | return re.escape(self.data) 61 | 62 | def dump(self, indent=0): 63 | return INDENT * indent + '\'' + self.data + '\'' 64 | -------------------------------------------------------------------------------- /Gelatin/parser/Parser.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | import os 21 | import codecs 22 | from simpleparse import parser 23 | from .Newline import Newline 24 | from .Indent import Indent 25 | from .Dedent import Dedent 26 | from .util import error 27 | 28 | _ebnf_file = os.path.join(os.path.dirname(__file__), 'syntax.ebnf') 29 | with open(_ebnf_file) as _thefile: 30 | _ebnf = _thefile.read() 31 | 32 | 33 | class Parser(parser.Parser): 34 | 35 | def __init__(self): 36 | self.indent = 0 37 | offside = ( 38 | ("NEWLINE", Newline(self).table()), 39 | ("INDENT", Indent(self).table()), 40 | ("DEDENT", Dedent(self).table()), 41 | ) 42 | parser.Parser.__init__(self, _ebnf, 'root', prebuilts=offside) 43 | 44 | def parse_string(self, input, compiler): 45 | compiler.reset() 46 | start, _, end = parser.Parser.parse(self, input, processor=compiler) 47 | if end < len(input): 48 | error(input, end) 49 | if 'input' not in compiler.context.grammars: 50 | error(input, end, 'Required grammar "input" not found.') 51 | return compiler.context 52 | 53 | def parse(self, filename, compiler, encoding='utf8'): 54 | with codecs.open(filename, 'r', encoding=encoding) as input_file: 55 | string = input_file.read() 56 | return self.parse_string(string, compiler) 57 | -------------------------------------------------------------------------------- /demo/tria/output1.yaml: -------------------------------------------------------------------------------- 1 | week: 2 | - '@number': '1' 3 | day: 4 | - weekday: 5 | '#text': Mo 6 | date: 7 | '#text': 04Jan10 8 | - weekday: 9 | '#text': Di 10 | date: 11 | '#text': 05Jan10 12 | - weekday: 13 | '#text': Mi 14 | date: 15 | '#text': 06Jan10 16 | - weekday: 17 | '#text': Do 18 | date: 19 | '#text': 07Jan10 20 | unit: 21 | - discipline: 22 | '#text': Run 23 | route: 24 | '#text': "Unterf\xFChrung" 25 | comment: 26 | '#text': LS3AsicsTrabuco, 5mm Neuschnee, rutschig 27 | time: 28 | '#text': 15:34(15:34),31:20(15.45), 29 | - discipline: 30 | '#text': Swim 31 | place: 32 | '#text': "Hallenbad Markgr\xF6ningen" 33 | distance: 34 | - '#text': 400 ES 35 | - '#text': 100 AS 36 | - weekday: 37 | '#text': Fr 38 | date: 39 | '#text': 08Jan10 40 | - weekday: 41 | '#text': Sa 42 | date: 43 | '#text': 09Jan10 44 | unit: 45 | discipline: 46 | '#text': Swim 47 | comment: 48 | '#text': "6. Einheit, Plan Dez09, R\xFCckentechnik" 49 | place: 50 | '#text': "Hallenbad Markgr\xF6ningen" 51 | distance: 52 | - '#text': 400 ES 53 | - '#text': 6*50 Technik 54 | - '#text': "8*50 GA1(25 Kraul, 25 R\xFCcken)" 55 | - '#text': "6*100 GA1(Kraul + R\xFCcken + Kraul + Brust im Wechsel)" 56 | - '#text': 25 tauchen, 25 tauchen, 29 tauchen, 33 tauchen 57 | - '#text': 100 AS 58 | - weekday: 59 | '#text': So 60 | date: 61 | '#text': 10Jan10 62 | - '@number': '2' 63 | day: 64 | - weekday: 65 | '#text': Mo 66 | date: 67 | '#text': 11Jan10 68 | - weekday: 69 | '#text': Mi 70 | date: 71 | '#text': 13Jan10 72 | - weekday: 73 | '#text': Do 74 | date: 75 | '#text': 14Jan10 76 | unit: 77 | discipline: 78 | '#text': Bike 79 | comment: 80 | '#text': "Feldwege + Stra\xDFe" 81 | material: 82 | '#text': Centurion 83 | route: 84 | '#text': "Feldwege nach Eglosheim, Monrepos, Bietigheim, Bissingen, dann Stra\xDF\ 85 | e nach Untermberg, Sachsenheim (Industriegebiet), Fontanis-Lager, Kreissel\ 86 | \ Oberriexingen, Unterriexingen." 87 | distance: 88 | '#text': 44.2 km 89 | average: 90 | '#text': 18.3 km/h 91 | - weekday: 92 | '#text': Fr 93 | date: 94 | '#text': 15Jan10 95 | - weekday: 96 | '#text': So 97 | date: 98 | '#text': 17Jan10 99 | -------------------------------------------------------------------------------- /demo/tria/output1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mo 5 | 04Jan10 6 | 7 | 8 | Di 9 | 05Jan10 10 | 11 | 12 | Mi 13 | 06Jan10 14 | 15 | 16 | Do 17 | 07Jan10 18 | 19 | Run 20 | Unterführung 21 | LS3AsicsTrabuco, 5mm Neuschnee, rutschig 22 | 23 | 24 | 25 | Swim 26 | Hallenbad Markgröningen 27 | 400 ES 28 | 100 AS 29 | 30 | 31 | 32 | Fr 33 | 08Jan10 34 | 35 | 36 | Sa 37 | 09Jan10 38 | 39 | Swim 40 | 6. Einheit, Plan Dez09, Rückentechnik 41 | Hallenbad Markgröningen 42 | 400 ES 43 | 6*50 Technik 44 | 8*50 GA1(25 Kraul, 25 Rücken) 45 | 6*100 GA1(Kraul + Rücken + Kraul + Brust im Wechsel) 46 | 25 tauchen, 25 tauchen, 29 tauchen, 33 tauchen 47 | 100 AS 48 | 49 | 50 | 51 | So 52 | 10Jan10 53 | 54 | 55 | 56 | 57 | Mo 58 | 11Jan10 59 | 60 | 61 | Mi 62 | 13Jan10 63 | 64 | 65 | Do 66 | 14Jan10 67 | 68 | Bike 69 | Feldwege + Straße 70 | Centurion 71 | Feldwege nach Eglosheim, Monrepos, Bietigheim, Bissingen, dann Straße nach Untermberg, Sachsenheim (Industriegebiet), Fontanis-Lager, Kreissel Oberriexingen, Unterriexingen. 72 | 44.2 km 73 | 18.3 km/h 74 | 75 | 76 | 77 | Fr 78 | 15Jan10 79 | 80 | 81 | So 82 | 17Jan10 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | import sys 21 | import os 22 | from setuptools import setup, find_packages 23 | from Gelatin.version import __version__ 24 | 25 | descr = ''' 26 | Gelatin converts text to a structured format, such as XML, JSON or YAML. 27 | '''.strip() 28 | 29 | # Run the setup. 30 | setup(name='Gelatin', 31 | version=__version__, 32 | description='Transform text files to XML, JSON, or YAML', 33 | long_description=descr, 34 | author='Samuel Abels', 35 | author_email='knipknap@gmail.com', 36 | license='MIT', 37 | package_dir={'Gelatin': 'Gelatin'}, 38 | package_data={'Gelatin': [os.path.join('parser', 'syntax.ebnf')]}, 39 | packages=find_packages(), 40 | scripts=['scripts/gel'], 41 | python_requires='>=3', 42 | install_requires = ['lxml', 43 | 'pyyaml', 44 | 'SimpleParse'], 45 | test_suite='tests', 46 | keywords=' '.join(['gelatin', 47 | 'gel', 48 | 'parser', 49 | 'lexer', 50 | 'xml', 51 | 'json', 52 | 'yaml', 53 | 'generator', 54 | 'syntax', 55 | 'text', 56 | 'transform']), 57 | url='https://github.com/knipknap/Gelatin', 58 | classifiers=[ 59 | 'Development Status :: 5 - Production/Stable', 60 | 'Intended Audience :: Developers', 61 | 'License :: OSI Approved :: MIT License', 62 | 'Programming Language :: Python :: 3', 63 | 'Topic :: Software Development :: Libraries', 64 | 'Topic :: Software Development :: Libraries :: Python Modules', 65 | 'Topic :: Text Processing :: Markup :: XML' 66 | ]) 67 | -------------------------------------------------------------------------------- /Gelatin/compiler/WhenStatement.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from __future__ import print_function 21 | from Gelatin import INDENT 22 | from .Token import Token 23 | 24 | 25 | class WhenStatement(Token): 26 | 27 | def __init__(self): 28 | self.matchlist = None 29 | self.statements = None 30 | self.on_leave = [] 31 | 32 | def _enter(self, context, debug): 33 | context.stack.append(self) 34 | if debug > 2: 35 | print("ENTER", 36 | self.__class__.__name__, 37 | self.matchlist.dump(), 38 | end='') 39 | 40 | def _leave(self, context, debug): 41 | for func, args in self.on_leave: 42 | func(*args) 43 | self.on_leave = [] 44 | context.stack.pop() 45 | if debug > 2: 46 | print("LEAVE", 47 | self.__class__.__name__, 48 | self.matchlist.dump(), 49 | end='') 50 | 51 | def _handle_match(self, context, match, debug): 52 | if not match: 53 | return 0 54 | self._enter(context, debug) 55 | context._match_before_notify(match) 56 | for statement in self.statements: 57 | result = statement.parse(context, debug) 58 | if result == 1: 59 | break 60 | elif result < 0: 61 | context._match_after_notify(match) 62 | self._leave(context, debug) 63 | return result 64 | context._match_after_notify(match) 65 | self._leave(context, debug) 66 | return 1 67 | 68 | def parse(self, context, debug=0): 69 | match = self.matchlist.when(context) 70 | return self._handle_match(context, match, debug) 71 | 72 | def dump(self, indent=0): 73 | res = INDENT * indent + 'match:\n' 74 | res += self.matchlist.dump(indent + 1) 75 | for statement in self.statements: 76 | res += statement.dump(indent + 2) + '\n' 77 | return res 78 | -------------------------------------------------------------------------------- /tests/demo_test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from __future__ import unicode_literals, print_function 21 | import sys 22 | import unittest 23 | import re 24 | import os 25 | import codecs 26 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) 27 | from Gelatin.util import compile, generate 28 | 29 | dirname = os.path.dirname(__file__) 30 | demo_dir = os.path.join(os.path.dirname(dirname), 'demo') 31 | 32 | def convert(filename, format): 33 | syntax_file = os.path.join(demo_dir, filename, 'syntax.gel') 34 | input_file = os.path.join(demo_dir, filename, 'input1.txt') 35 | syntax = compile(syntax_file) 36 | try: 37 | return generate(syntax, input_file, format) 38 | except Exception as e: 39 | print("ERROR parsing", input_file) 40 | raise 41 | 42 | 43 | class DemoTest(unittest.TestCase): 44 | 45 | def setUp(self): 46 | self.demos = os.listdir(demo_dir) 47 | self.maxDiff = None 48 | 49 | def testDemos(self): 50 | for filename in self.demos: 51 | for format in ('xml', 'yaml', 'json'): 52 | output = convert(filename, format) 53 | # In Python 3.3, json.dumps() would output trailing whitespace 54 | # in the generated JSON. As a workaround, we remove this here... 55 | output = output.replace(' \n', '\n') 56 | output_name = 'output1.' + format 57 | output_file = os.path.join(demo_dir, filename, output_name) 58 | #with codecs.open(output_file, 'w', encoding='utf-8') as fp: 59 | # fp.write(output) 60 | with codecs.open(output_file, encoding='utf-8') as fp: 61 | expected = fp.read() 62 | # print(output_file, repr(output)) 63 | # print(output_file, repr(expected)) 64 | self.assertEqual(output, expected) 65 | 66 | def suite(): 67 | return unittest.TestLoader().loadTestsFromTestCase(DemoTest) 68 | if __name__ == '__main__': 69 | unittest.TextTestRunner(verbosity=2).run(suite()) 70 | -------------------------------------------------------------------------------- /Gelatin/compiler/Grammar.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | from __future__ import print_function 21 | from Gelatin import INDENT 22 | from .Token import Token 23 | 24 | 25 | class Grammar(Token): 26 | 27 | def __init__(self): 28 | self.name = None 29 | self.inherit = None 30 | self.statements = None 31 | self.on_leave = [] 32 | 33 | def get_statements(self, context): 34 | if not self.inherit: 35 | return self.statements 36 | inherited = context.grammars[self.inherit].get_statements(context) 37 | return inherited + self.statements 38 | 39 | def _enter(self, context, debug): 40 | context.stack.append(self) 41 | if debug > 1: 42 | print("ENTER", self.__class__.__name__, self.name) 43 | 44 | def _leave(self, context, debug): 45 | for func, args in self.on_leave: 46 | func(*args) 47 | self.on_leave = [] 48 | context.stack.pop() 49 | if debug > 1: 50 | print("LEAVE", self.__class__.__name__, self.name) 51 | 52 | def parse(self, context, debug=0): 53 | self._enter(context, debug) 54 | statements = self.get_statements(context) 55 | matched = True 56 | while matched: 57 | if context._eof(): 58 | self._leave(context, debug) 59 | return 60 | matched = False 61 | # context._msg(self.name) 62 | for statement in statements: 63 | result = statement.parse(context, debug) 64 | if result == 1: 65 | matched = True 66 | break 67 | elif result < 0: 68 | self._leave(context, debug) 69 | return result + 1 70 | context._error('no match found, context was ' + self.name) 71 | 72 | def dump(self, indent=0): 73 | res = INDENT * indent + 'grammar ' + self.name 74 | if self.inherit: 75 | res += '(' + self.inherit + ')' 76 | res += ':\n' 77 | for statement in self.statements: 78 | res += statement.dump(indent + 1) 79 | return res 80 | -------------------------------------------------------------------------------- /Gelatin/parser/util.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | try: 21 | import re2 as re 22 | except ImportError: 23 | import re 24 | from Gelatin import INDENT_WIDTH 25 | 26 | whitespace_re = re.compile(' *') 27 | 28 | 29 | def _format(buffer, end, msg): 30 | line_start = buffer.rfind('\n', 0, end) + 1 31 | line_end = buffer.find('\n', line_start) 32 | line_no = buffer.count('\n', 0, end) + 1 33 | line = buffer[line_start:line_end] 34 | offset = end - line_start 35 | mark = ' ' + ' ' * offset + '^' 36 | return '%s in line %d:\n%s\n%s' % (msg, line_no, repr(line), mark) 37 | 38 | 39 | def say(buffer, end, msg): 40 | print(_format(buffer, end, msg)) 41 | 42 | 43 | def error(buffer, end, msg='Syntax error'): 44 | msg = _format(buffer, end, msg) 45 | raise Exception(msg) 46 | 47 | 48 | def eat_indent(buffer, start, end, expected_indent=None): 49 | # In Py3, using this may be more efficient: 50 | # result = whitespace_re.match(buffer, start, end) 51 | # In Py2/PyRe2, the character count differs from len(str), 52 | # or str.rfind() if the buffer contains unicode chars. 53 | result = whitespace_re.match(buffer[start:end]) 54 | if result is None: 55 | # pyre2 returns None if the start parameter to match() is larger 56 | # than the length of the buffer. 57 | return start 58 | whitespace = result.group(0) 59 | whitespace_len = len(whitespace) 60 | indent = whitespace_len / INDENT_WIDTH 61 | if whitespace_len % INDENT_WIDTH != 0: 62 | msg = 'indent must be a multiple of %d' % INDENT_WIDTH 63 | error(buffer, start, msg) 64 | if expected_indent is None or expected_indent == indent: 65 | return start + whitespace_len 66 | return start 67 | 68 | 69 | def count_indent(buffer, start): 70 | indent = start - buffer.rfind('\n', 0, start) - 1 71 | if indent % INDENT_WIDTH != 0: 72 | msg = 'indent must be a multiple of %d, is %d' % (INDENT_WIDTH, indent) 73 | error(buffer, start, msg) 74 | if indent / INDENT_WIDTH > 2: 75 | msg = 'maximum indent (2 levels) exceeded.' 76 | error(buffer, start, msg) 77 | return indent / INDENT_WIDTH 78 | -------------------------------------------------------------------------------- /scripts/gel: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright (C) 2010 Samuel Abels. 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License version 2, as 6 | # published by the Free Software Foundation. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 16 | import sys 17 | import os 18 | import datetime 19 | sys.path.insert(0, 'src') 20 | from optparse import OptionParser 21 | from Gelatin import __version__, generator 22 | from Gelatin.generator import Builder 23 | from Gelatin.util import compile 24 | 25 | usage = '''%prog [options] filename [filename ...]''' 26 | parser = OptionParser(usage = usage, version = __version__) 27 | parser.add_option('--syntax', '-s', 28 | dest = 'syntax', 29 | metavar = 'FILE', 30 | default = None, 31 | help = ''' 32 | The file containing the syntax for parsing the input 33 | '''.strip()) 34 | parser.add_option('--format', '-f', 35 | dest = 'format', 36 | default = 'xml', 37 | metavar = 'FORMAT', 38 | help = ''' 39 | The output format. Valid values are: xml json yaml none. Default is xml. 40 | '''.strip()) 41 | parser.add_option('--debug', 42 | dest = 'debug', 43 | type = 'int', 44 | metavar = 'NUM', 45 | default = 0, 46 | help = ''' 47 | Print debug info. 48 | '''.strip()) 49 | 50 | if __name__ == '__main__': 51 | # Parse options. 52 | options, args = parser.parse_args(sys.argv) 53 | args.pop(0) 54 | 55 | if not options.syntax: 56 | parser.error('missing switch -s') 57 | if not os.path.exists(options.syntax): 58 | parser.error('no such file or directory: ' + options.syntax) 59 | if not os.path.isfile(options.syntax): 60 | parser.error('not a valid input file: ' + options.syntax) 61 | 62 | input_files = args 63 | if not input_files: 64 | parser.error('missing input file') 65 | 66 | serializer = generator.new(options.format) 67 | if serializer is None: 68 | parser.error('invalid output format: ' + options.format) 69 | 70 | def dbg(*msg): 71 | if options.debug: 72 | now = str(datetime.datetime.now()) 73 | sys.stderr.write(now + ' ' + ' '.join(msg) + '\n') 74 | 75 | start = datetime.datetime.now() 76 | dbg("Compiling", options.syntax + '...') 77 | converter = compile(options.syntax) 78 | for input_file in input_files: 79 | dbg("Parsing", input_file + '...') 80 | builder = Builder() 81 | converter.parse(input_file, builder, debug=options.debug) 82 | if options.format != 'none': 83 | dbg("Generating output...") 84 | print(builder.serialize(serializer)) 85 | dbg('Total: ' + str(datetime.datetime.now() - start)) 86 | -------------------------------------------------------------------------------- /docs/quick.rst: -------------------------------------------------------------------------------- 1 | Quick Start 2 | =========== 3 | 4 | Suppose you want to convert the following text file to XML:: 5 | 6 | User 7 | ---- 8 | Name: John, Lastname: Doe 9 | Office: 1st Ave 10 | Birth date: 1978-01-01 11 | 12 | User 13 | ---- 14 | Name: Jane, Lastname: Foo 15 | Office: 2nd Ave 16 | Birth date: 1970-01-01 17 | 18 | The following Gelatin syntax does the job:: 19 | 20 | # Define commonly used data types. This is optional, but 21 | # makes your life a litte easier by allowing to reuse regular 22 | # expressions in the grammar. 23 | define nl /[\r\n]/ 24 | define ws /\s+/ 25 | define fieldname /[\w ]+/ 26 | define value /[^\r\n,]+/ 27 | define field_end /[\r\n,] */ 28 | 29 | grammar user: 30 | match 'Name:' ws value field_end: 31 | out.add_attribute('.', 'firstname', '$2') 32 | match 'Lastname:' ws value field_end: 33 | out.add_attribute('.', 'lastname', '$2') 34 | match fieldname ':' ws value field_end: 35 | out.add('$0', '$3') 36 | match nl: 37 | do.return() 38 | 39 | # The grammar named "input" is the entry point for the converter. 40 | grammar input: 41 | match 'User' nl '----' nl: 42 | out.open('user') 43 | user() 44 | 45 | Explanation: 46 | 47 | #. **"grammar input:"** is the entry point for the converter. 48 | #. **"match"** statements in each grammar are executed sequentially. If 49 | a match is found, the indented statements in the match block are 50 | executed. After reaching the end of a match block, the grammar 51 | restarts at the top of the grammar block. 52 | #. If the end of a grammar is reached before the end of the input 53 | document was reached, an error is raised. 54 | #. **out.add('$0', '$3')** creates a node in the XML (or JSON, or YAML) 55 | if it does not yet exist. The name of the node is the value of the 56 | first matched field (the fieldname, in this case). The data of the 57 | node is the value of the fourth matched field. 58 | #. **out.open('user')** creates a "user" node in the output and selects 59 | it such that all following "add" statements generate output relative 60 | to the "user" node. Gelatin leaves the user node upon reaching the 61 | out.leave() statement. 62 | #. **user()** calls the grammar named "user". 63 | 64 | This produces the following output: 65 | 66 | :: 67 | 68 | 69 | 70 | 1st Ave 71 | 1978-01-01 72 | 73 | 74 | 2nd Ave 75 | 1970-01-01 76 | 77 | 78 | 79 | Starting the transformation 80 | --------------------------- 81 | 82 | The following command converts the input to XML: 83 | 84 | :: 85 | 86 | gel -s mysyntax.gel input.txt 87 | 88 | The same for JSON or YAML: 89 | 90 | :: 91 | 92 | gel -s mysyntax.gel -f json input.txt 93 | gel -s mysyntax.gel -f yaml input.txt 94 | 95 | Using Gelatin as a Python Module 96 | -------------------------------- 97 | 98 | Gelatin also provides a Python API for transforming the text:: 99 | 100 | from Gelatin import generator 101 | from Gelatin.util import compile 102 | 103 | # Parse your .gel file. 104 | syntax = compile('syntax.gel') 105 | 106 | # Convert your input file to XML. 107 | builder = generator.Xml() 108 | syntax.parse('input.txt', builder) 109 | print builder.serialize() 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gelatin 2 | 3 | [![Build Status](https://travis-ci.org/knipknap/Gelatin.svg?branch=master)](https://travis-ci.org/knipknap/Gelatin) 4 | [![Coverage Status](https://coveralls.io/repos/github/knipknap/Gelatin/badge.svg?branch=master)](https://coveralls.io/github/knipknap/Gelatin?branch=master) 5 | [![Code Climate](https://lima.codeclimate.com/github/knipknap/Gelatin/badges/gpa.svg)](https://lima.codeclimate.com/github/knipknap/Gelatin) 6 | [![Documentation Status](https://readthedocs.org/projects/gelatin/badge/?version=latest)](http://gelatin.readthedocs.io/en/latest/?badge=latest) 7 | 8 | ## Summary 9 | 10 | Gelatin is a parser generator for converting text to a structured 11 | format such as XML, JSON or YAML. 12 | 13 | ## Do you need commercial support? 14 | 15 | Gelatin is supported by [Procedure 8](https://procedure8.com). Get in touch if you need anything! 16 | 17 | ## Converting Text to XML, JSON, or YAML 18 | 19 | Gelatin is a combined lexer, parser, and output generator. 20 | Gelatin defines a simple language for converting text into a structured formats. 21 | 22 | ### Example 23 | 24 | Suppose you want to convert the following text file to XML: 25 | 26 | ``` 27 | User 28 | ---- 29 | Name: John, Lastname: Doe 30 | Office: 1st Ave 31 | Birth date: 1978-01-01 32 | 33 | User 34 | ---- 35 | Name: Jane, Lastname: Foo 36 | Office: 2nd Ave 37 | Birth date: 1970-01-01 38 | ``` 39 | 40 | ### The following Gelatin syntax does the job: 41 | 42 | ``` 43 | # Define commonly used data types. This is optional, but 44 | # makes your life a litte easier by allowing to reuse regular 45 | # expressions in the grammar. 46 | define nl /[\r\n]/ 47 | define ws /\s+/ 48 | define fieldname /[\w ]+/ 49 | define value /[^\r\n,]+/ 50 | define field_end /[\r\n,] */ 51 | 52 | grammar user: 53 | match 'Name:' ws value field_end: 54 | out.add_attribute('.', 'firstname', '$2') 55 | match 'Lastname:' ws value field_end: 56 | out.add_attribute('.', 'lastname', '$2') 57 | match fieldname ':' ws value field_end: 58 | out.add('$0', '$3') 59 | match nl: 60 | do.return() 61 | 62 | # The grammar named "input" is the entry point for the converter. 63 | grammar input: 64 | match 'User' nl '----' nl: 65 | out.open('user') 66 | user() 67 | ``` 68 | 69 | ### Explanation 70 | 71 | * **"grammar input:"** is the entry point for the converter. 72 | * **"match"** statements in each grammar are executed sequentially. If a match is found, the indented statements in the match block are executed. After reaching the end of a match block, the grammar restarts at the top of the grammar block. 73 | * If the end of a grammar is reached before the end of the input document was reached, an error is raised. 74 | * **"out.add('$0', '$3')"** creates a node in the XML (or JSON, or YAML) if it does not yet exist. The name of the node is the value of the first matched field (the fieldname, in this case). The data of the node is the value of the fourth matched field. 75 | * **"out.open('user')"** creates a "user" node in the output and selects it such that all following "add" statements generate output relative to the "user" node. Gelatin leaves the user node upon reaching the out.leave() statement. 76 | * **"user()"** calls the grammar named "user". 77 | 78 | This produces the following output: 79 | 80 | ```xml 81 | 82 | 83 | 1st Ave 84 | 1978-01-01 85 | 86 | 87 | 2nd Ave 88 | 1970-01-01 89 | 90 | 91 | ``` 92 | 93 | ### Method 1: Starting the transformation using the CLI tool 94 | 95 | The following command converts the input to XML: 96 | 97 | ``` 98 | gel -s mysyntax.gel input.txt 99 | ``` 100 | 101 | The same for JSON or YAML: 102 | 103 | ``` 104 | gel -s mysyntax.gel -f json input.txt 105 | gel -s mysyntax.gel -f yaml input.txt 106 | ``` 107 | 108 | ### Method 2: Starting the transformation using Gelatin as a Python Module 109 | 110 | Gelatin also provides a Python API for transforming the text: 111 | 112 | ```python 113 | from Gelatin.util import compile, generate 114 | 115 | # Parse your .gel file. 116 | syntax = compile('syntax.gel') 117 | 118 | # Convert your input file to XML, YAML, and JSON. 119 | print(generate(syntax, 'input.txt')) 120 | print(generate(syntax, 'input.txt', format='yaml')) 121 | print(generate(syntax, 'input.txt', format='json')) 122 | ``` 123 | 124 | ## Documentation 125 | 126 | For full documentation please refer to 127 | 128 | http://gelatin.readthedocs.io 129 | -------------------------------------------------------------------------------- /Gelatin/util.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | import codecs 21 | from . import generator 22 | from .generator import Builder 23 | from .parser import Parser 24 | from .compiler import SyntaxCompiler 25 | 26 | 27 | def compile_string(syntax): 28 | """ 29 | Builds a converter from the given syntax and returns it. 30 | 31 | :type syntax: str 32 | :param syntax: A Gelatin syntax. 33 | :rtype: compiler.Context 34 | :return: The compiled converter. 35 | """ 36 | return Parser().parse_string(syntax, SyntaxCompiler()) 37 | 38 | 39 | def compile(syntax_file, encoding='utf8'): 40 | """ 41 | Like compile_string(), but reads the syntax from the file with the 42 | given name. 43 | 44 | :type syntax_file: str 45 | :param syntax_file: Name of a file containing Gelatin syntax. 46 | :type encoding: str 47 | :param encoding: Character encoding of the syntax file. 48 | :rtype: compiler.Context 49 | :return: The compiled converter. 50 | """ 51 | return Parser().parse(syntax_file, 52 | SyntaxCompiler(), 53 | encoding=encoding) 54 | 55 | 56 | def generate(converter, input_file, format='xml', encoding='utf8'): 57 | """ 58 | Given a converter (as returned by compile()), this function reads 59 | the given input file and converts it to the requested output format. 60 | 61 | Supported output formats are 'xml', 'yaml', 'json', or 'none'. 62 | 63 | :type converter: compiler.Context 64 | :param converter: The compiled converter. 65 | :type input_file: str 66 | :param input_file: Name of a file to convert. 67 | :type format: str 68 | :param format: The output format. 69 | :type encoding: str 70 | :param encoding: Character encoding of the input file. 71 | :rtype: str 72 | :return: The resulting output. 73 | """ 74 | with codecs.open(input_file, encoding=encoding) as thefile: 75 | return generate_string(converter, thefile.read(), format=format) 76 | 77 | 78 | def generate_to_file(converter, 79 | input_file, 80 | output_file, 81 | format='xml', 82 | in_encoding='utf8', 83 | out_encoding='utf8'): 84 | """ 85 | Like generate(), but writes the output to the given output file 86 | instead. 87 | 88 | :type converter: compiler.Context 89 | :param converter: The compiled converter. 90 | :type input_file: str 91 | :param input_file: Name of a file to convert. 92 | :type output_file: str 93 | :param output_file: The output filename. 94 | :type format: str 95 | :param format: The output format. 96 | :type in_encoding: str 97 | :param in_encoding: Character encoding of the input file. 98 | :type out_encoding: str 99 | :param out_encoding: Character encoding of the output file. 100 | :rtype: str 101 | :return: The resulting output. 102 | """ 103 | with codecs.open(output_file, 'w', encoding=out_encoding) as thefile: 104 | result = generate(converter, input_file, format=format, encoding=in_encoding) 105 | thefile.write(result) 106 | 107 | 108 | def generate_string(converter, input, format='xml'): 109 | """ 110 | Like generate(), but reads the input from a string instead of 111 | from a file. 112 | 113 | :type converter: compiler.Context 114 | :param converter: The compiled converter. 115 | :type input: str 116 | :param input: The string to convert. 117 | :type format: str 118 | :param format: The output format. 119 | :rtype: str 120 | :return: The resulting output. 121 | """ 122 | serializer = generator.new(format) 123 | if serializer is None: 124 | raise TypeError('invalid output format ' + repr(format)) 125 | builder = Builder() 126 | converter.parse_string(input, builder) 127 | return builder.serialize(serializer) 128 | 129 | 130 | def generate_string_to_file(converter, 131 | input, 132 | output_file, 133 | format='xml', 134 | out_encoding='utf8'): 135 | """ 136 | Like generate(), but reads the input from a string instead of 137 | from a file, and writes the output to the given output file. 138 | 139 | :type converter: compiler.Context 140 | :param converter: The compiled converter. 141 | :type input: str 142 | :param input: The string to convert. 143 | :type output_file: str 144 | :param output_file: The output filename. 145 | :type format: str 146 | :param format: The output format. 147 | :type out_encoding: str 148 | :param out_encoding: Character encoding of the output file. 149 | :rtype: str 150 | :return: The resulting output. 151 | """ 152 | with codecs.open(output_file, 'w', encoding=out_encoding) as thefile: 153 | result = generate_string(converter, input, format=format) 154 | thefile.write(result) 155 | -------------------------------------------------------------------------------- /demo/tria/output1.json: -------------------------------------------------------------------------------- 1 | { 2 | "week": [ 3 | { 4 | "@number": "1", 5 | "day": [ 6 | { 7 | "weekday": { 8 | "#text": "Mo" 9 | }, 10 | "date": { 11 | "#text": "04Jan10" 12 | } 13 | }, 14 | { 15 | "weekday": { 16 | "#text": "Di" 17 | }, 18 | "date": { 19 | "#text": "05Jan10" 20 | } 21 | }, 22 | { 23 | "weekday": { 24 | "#text": "Mi" 25 | }, 26 | "date": { 27 | "#text": "06Jan10" 28 | } 29 | }, 30 | { 31 | "weekday": { 32 | "#text": "Do" 33 | }, 34 | "date": { 35 | "#text": "07Jan10" 36 | }, 37 | "unit": [ 38 | { 39 | "discipline": { 40 | "#text": "Run" 41 | }, 42 | "route": { 43 | "#text": "Unterführung" 44 | }, 45 | "comment": { 46 | "#text": "LS3AsicsTrabuco, 5mm Neuschnee, rutschig" 47 | }, 48 | "time": { 49 | "#text": "15:34(15:34),31:20(15.45)," 50 | } 51 | }, 52 | { 53 | "discipline": { 54 | "#text": "Swim" 55 | }, 56 | "place": { 57 | "#text": "Hallenbad Markgröningen" 58 | }, 59 | "distance": [ 60 | { 61 | "#text": "400 ES" 62 | }, 63 | { 64 | "#text": "100 AS" 65 | } 66 | ] 67 | } 68 | ] 69 | }, 70 | { 71 | "weekday": { 72 | "#text": "Fr" 73 | }, 74 | "date": { 75 | "#text": "08Jan10" 76 | } 77 | }, 78 | { 79 | "weekday": { 80 | "#text": "Sa" 81 | }, 82 | "date": { 83 | "#text": "09Jan10" 84 | }, 85 | "unit": { 86 | "discipline": { 87 | "#text": "Swim" 88 | }, 89 | "comment": { 90 | "#text": "6. Einheit, Plan Dez09, Rückentechnik" 91 | }, 92 | "place": { 93 | "#text": "Hallenbad Markgröningen" 94 | }, 95 | "distance": [ 96 | { 97 | "#text": "400 ES" 98 | }, 99 | { 100 | "#text": "6*50 Technik" 101 | }, 102 | { 103 | "#text": "8*50 GA1(25 Kraul, 25 Rücken)" 104 | }, 105 | { 106 | "#text": "6*100 GA1(Kraul + Rücken + Kraul + Brust im Wechsel)" 107 | }, 108 | { 109 | "#text": "25 tauchen, 25 tauchen, 29 tauchen, 33 tauchen" 110 | }, 111 | { 112 | "#text": "100 AS" 113 | } 114 | ] 115 | } 116 | }, 117 | { 118 | "weekday": { 119 | "#text": "So" 120 | }, 121 | "date": { 122 | "#text": "10Jan10" 123 | } 124 | } 125 | ] 126 | }, 127 | { 128 | "@number": "2", 129 | "day": [ 130 | { 131 | "weekday": { 132 | "#text": "Mo" 133 | }, 134 | "date": { 135 | "#text": "11Jan10" 136 | } 137 | }, 138 | { 139 | "weekday": { 140 | "#text": "Mi" 141 | }, 142 | "date": { 143 | "#text": "13Jan10" 144 | } 145 | }, 146 | { 147 | "weekday": { 148 | "#text": "Do" 149 | }, 150 | "date": { 151 | "#text": "14Jan10" 152 | }, 153 | "unit": { 154 | "discipline": { 155 | "#text": "Bike" 156 | }, 157 | "comment": { 158 | "#text": "Feldwege + Straße" 159 | }, 160 | "material": { 161 | "#text": "Centurion" 162 | }, 163 | "route": { 164 | "#text": "Feldwege nach Eglosheim, Monrepos, Bietigheim, Bissingen, dann Straße nach Untermberg, Sachsenheim (Industriegebiet), Fontanis-Lager, Kreissel Oberriexingen, Unterriexingen." 165 | }, 166 | "distance": { 167 | "#text": "44.2 km" 168 | }, 169 | "average": { 170 | "#text": "18.3 km/h" 171 | } 172 | } 173 | }, 174 | { 175 | "weekday": { 176 | "#text": "Fr" 177 | }, 178 | "date": { 179 | "#text": "15Jan10" 180 | } 181 | }, 182 | { 183 | "weekday": { 184 | "#text": "So" 185 | }, 186 | "date": { 187 | "#text": "17Jan10" 188 | } 189 | } 190 | ] 191 | } 192 | ] 193 | } -------------------------------------------------------------------------------- /Gelatin/compiler/SyntaxCompiler.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | import re 21 | from simpleparse.dispatchprocessor import DispatchProcessor, getString, singleMap 22 | from .Function import Function 23 | from .Grammar import Grammar 24 | from .SkipStatement import SkipStatement 25 | from .WhenStatement import WhenStatement 26 | from .MatchStatement import MatchStatement 27 | from .MatchFieldList import MatchFieldList 28 | from .MatchList import MatchList 29 | from .Number import Number 30 | from .Regex import Regex 31 | from .String import String 32 | from .Context import Context 33 | 34 | """ 35 | Indent handling: 36 | o NEWLINE: 37 | - If the amount of indent matches the previous line, parse the \n 38 | and skip all indent. 39 | - If the amount of indent does NOT match the previous line, parse 40 | the \n and stay at the beginning of the new line to let INDENT 41 | or DEDENT figure it out. 42 | o INDENT: Skips all indent, then looks backward to update the indent 43 | count. Checks to make sure that the indent was increased. 44 | o DEDENT: Like INDENT, except it does not check for errors. 45 | """ 46 | 47 | 48 | class SyntaxCompiler(DispatchProcessor): 49 | 50 | """ 51 | Processor sub-class defining processing functions for the productions. 52 | """ 53 | 54 | def __init__(self): 55 | self.context = None 56 | 57 | def reset(self): 58 | self.context = Context() 59 | 60 | def _regex(self, token, buffer): 61 | tag, left, right, sublist = token 62 | regex = Regex() 63 | regex.data = getString(sublist[0], buffer) 64 | return regex 65 | 66 | def _string(self, token, buffer): 67 | tag, left, right, sublist = token 68 | string = getString(sublist[0], buffer) 69 | return String(self.context, string) 70 | 71 | def _varname(self, token, buffer): 72 | varname = getString(token, buffer) 73 | return self.context.lexicon[varname] 74 | 75 | def _number(self, token, buffer): 76 | number = getString(token, buffer) 77 | return Number(int(number)) 78 | 79 | def _expression(self, token, buffer): 80 | tag = token[0] 81 | if tag == 'string': 82 | return self._string(token, buffer) 83 | elif tag == 'regex': 84 | return self._regex(token, buffer) 85 | elif tag == 'varname': 86 | return self._varname(token, buffer) 87 | elif tag == 'number': 88 | return self._number(token, buffer) 89 | else: 90 | raise Exception('BUG: invalid token %s' % tag) 91 | 92 | def _match_field_list(self, token, buffer, flags): 93 | tag, left, right, sublist = token 94 | field_list = MatchFieldList(flags) 95 | for field in sublist: 96 | expression = self._expression(field, buffer) 97 | field_list.expressions.append(expression) 98 | return field_list 99 | 100 | def _match_list(self, token, buffer, flags): 101 | tag, left, right, sublist = token 102 | matchlist = MatchList() 103 | for field_list in sublist: 104 | field_list = self._match_field_list(field_list, buffer, flags) 105 | matchlist.field_lists.append(field_list) 106 | return matchlist 107 | 108 | def _match_stmt(self, token, buffer, flags=0): 109 | tag, left, right, sublist = token 110 | matcher = MatchStatement() 111 | matcher.matchlist = self._match_list(sublist[0], buffer, flags) 112 | matcher.statements = self._suite(sublist[1], buffer) 113 | return matcher 114 | 115 | def _when_stmt(self, token, buffer, flags=0): 116 | tag, left, right, sublist = token 117 | matcher = WhenStatement() 118 | matcher.matchlist = self._match_list(sublist[0], buffer, flags) 119 | matcher.statements = self._suite(sublist[1], buffer) 120 | return matcher 121 | 122 | def _skip_stmt(self, token, buffer): 123 | tag, left, right, sublist = token 124 | matcher = SkipStatement() 125 | matcher.match = self._expression(sublist[0], buffer) 126 | return matcher 127 | 128 | def _function(self, token, buffer): 129 | tag, left, right, sublist = token 130 | function = Function() 131 | function.name = getString(sublist[0], buffer) 132 | if len(sublist) == 1: 133 | return function 134 | for arg in sublist[1][3]: 135 | expression = self._expression(arg, buffer) 136 | function.args.append(expression) 137 | return function 138 | 139 | def _inherit(self, token, buffer): 140 | tag, left, right, sublist = token 141 | return getString(sublist[0], buffer) 142 | 143 | def _suite(self, token, buffer): 144 | tag, left, right, sublist = token 145 | statements = [] 146 | for token in sublist: 147 | tag = token[0] 148 | if tag == 'match_stmt': 149 | statement = self._match_stmt(token, buffer) 150 | elif tag == 'imatch_stmt': 151 | statement = self._match_stmt(token, buffer, re.I) 152 | elif tag == 'when_stmt': 153 | statement = self._when_stmt(token, buffer) 154 | elif tag == 'skip_stmt': 155 | statement = self._skip_stmt(token, buffer) 156 | elif tag == 'function': 157 | statement = self._function(token, buffer) 158 | else: 159 | raise Exception('BUG: invalid token %s' % tag) 160 | statements.append(statement) 161 | return statements 162 | 163 | def define_stmt(self, token, buffer): 164 | tag, left, right, sublist = token 165 | name_tup, value_tup = sublist 166 | value_tag = value_tup[0] 167 | name = getString(name_tup, buffer) 168 | value = getString(value_tup, buffer) 169 | if value_tag == 'regex': 170 | value = self._regex(value_tup, buffer) 171 | elif value_tag == 'varname': 172 | if value not in self.context.lexicon: 173 | _error(buffer, value_tup[1], 'no such variable') 174 | value = self.context.lexicon[value] 175 | else: 176 | raise Exception('BUG: invalid token %s' % value_tag) 177 | self.context.lexicon[name] = value 178 | 179 | def grammar_stmt(self, token, buffer): 180 | tag, left, right, sublist = token 181 | map = singleMap(sublist) 182 | grammar = Grammar() 183 | grammar.name = getString(map['varname'], buffer) 184 | grammar.statements = self._suite(map['suite'], buffer) 185 | if 'inherit' in map: 186 | grammar.inherit = self._inherit(map['inherit'], buffer) 187 | self.context.grammars[grammar.name] = grammar 188 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help 23 | help: 24 | @echo "Please use \`make ' where is one of" 25 | @echo " html to make standalone HTML files" 26 | @echo " dirhtml to make HTML files named index.html in directories" 27 | @echo " singlehtml to make a single large HTML file" 28 | @echo " pickle to make pickle files" 29 | @echo " json to make JSON files" 30 | @echo " htmlhelp to make HTML files and a HTML help project" 31 | @echo " qthelp to make HTML files and a qthelp project" 32 | @echo " applehelp to make an Apple Help Book" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | @echo " coverage to run coverage check of the documentation (if enabled)" 49 | 50 | .PHONY: clean 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | .PHONY: apidoc 55 | apidoc: 56 | sphinx-apidoc -fo . ../Gelatin 57 | 58 | .PHONY: html 59 | html: 60 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 63 | 64 | .PHONY: dirhtml 65 | dirhtml: 66 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 67 | @echo 68 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 69 | 70 | .PHONY: singlehtml 71 | singlehtml: 72 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 73 | @echo 74 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 75 | 76 | .PHONY: pickle 77 | pickle: 78 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 79 | @echo 80 | @echo "Build finished; now you can process the pickle files." 81 | 82 | .PHONY: json 83 | json: 84 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 85 | @echo 86 | @echo "Build finished; now you can process the JSON files." 87 | 88 | .PHONY: htmlhelp 89 | htmlhelp: 90 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 91 | @echo 92 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 93 | ".hhp project file in $(BUILDDIR)/htmlhelp." 94 | 95 | .PHONY: qthelp 96 | qthelp: 97 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 98 | @echo 99 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 100 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 101 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Gelatin.qhcp" 102 | @echo "To view the help file:" 103 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Gelatin.qhc" 104 | 105 | .PHONY: applehelp 106 | applehelp: 107 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 108 | @echo 109 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 110 | @echo "N.B. You won't be able to view it unless you put it in" \ 111 | "~/Library/Documentation/Help or install it in your application" \ 112 | "bundle." 113 | 114 | .PHONY: devhelp 115 | devhelp: 116 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 117 | @echo 118 | @echo "Build finished." 119 | @echo "To view the help file:" 120 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Gelatin" 121 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Gelatin" 122 | @echo "# devhelp" 123 | 124 | .PHONY: epub 125 | epub: 126 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 127 | @echo 128 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 129 | 130 | .PHONY: latex 131 | latex: 132 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 133 | @echo 134 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 135 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 136 | "(use \`make latexpdf' here to do that automatically)." 137 | 138 | .PHONY: latexpdf 139 | latexpdf: 140 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 141 | @echo "Running LaTeX files through pdflatex..." 142 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 143 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 144 | 145 | .PHONY: latexpdfja 146 | latexpdfja: 147 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 148 | @echo "Running LaTeX files through platex and dvipdfmx..." 149 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 150 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 151 | 152 | .PHONY: text 153 | text: 154 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 155 | @echo 156 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 157 | 158 | .PHONY: man 159 | man: 160 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 161 | @echo 162 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 163 | 164 | .PHONY: texinfo 165 | texinfo: 166 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 167 | @echo 168 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 169 | @echo "Run \`make' in that directory to run these through makeinfo" \ 170 | "(use \`make info' here to do that automatically)." 171 | 172 | .PHONY: info 173 | info: 174 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 175 | @echo "Running Texinfo files through makeinfo..." 176 | make -C $(BUILDDIR)/texinfo info 177 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 178 | 179 | .PHONY: gettext 180 | gettext: 181 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 182 | @echo 183 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 184 | 185 | .PHONY: changes 186 | changes: 187 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 188 | @echo 189 | @echo "The overview file is in $(BUILDDIR)/changes." 190 | 191 | .PHONY: linkcheck 192 | linkcheck: 193 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 194 | @echo 195 | @echo "Link check complete; look for any errors in the above output " \ 196 | "or in $(BUILDDIR)/linkcheck/output.txt." 197 | 198 | .PHONY: doctest 199 | doctest: 200 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 201 | @echo "Testing of doctests in the sources finished, look at the " \ 202 | "results in $(BUILDDIR)/doctest/output.txt." 203 | 204 | .PHONY: coverage 205 | coverage: 206 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 207 | @echo "Testing of coverage in the sources finished, look at the " \ 208 | "results in $(BUILDDIR)/coverage/python.txt." 209 | 210 | .PHONY: xml 211 | xml: 212 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 213 | @echo 214 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 215 | 216 | .PHONY: pseudoxml 217 | pseudoxml: 218 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 219 | @echo 220 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 221 | -------------------------------------------------------------------------------- /Gelatin/compiler/Context.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | import sys 21 | import codecs 22 | 23 | 24 | def do_next(context): 25 | return 0 26 | 27 | 28 | def do_skip(context): 29 | return 1 30 | 31 | 32 | def do_fail(context, message='No matching statement found'): 33 | context._error(message) 34 | 35 | 36 | def do_say(context, message): 37 | context._msg(message) 38 | return 0 39 | 40 | 41 | def do_warn(context, message): 42 | context._warn(message) 43 | return 0 44 | 45 | 46 | def do_return(context, levels=1): 47 | # print "do.return():", -levels 48 | return -levels 49 | 50 | 51 | def out_create(context, path, data=None): 52 | # print "out.create():", path, data 53 | context.builder.create(path, data) 54 | context.builder.enter(path) 55 | context._trigger(context.on_add, context.re_stack[-1]) 56 | context.builder.leave() 57 | return 0 58 | 59 | 60 | def out_replace(context, path, data=None): 61 | # print "out.replace():", path, data 62 | context.builder.add(path, data, replace=True) 63 | context.builder.enter(path) 64 | context._trigger(context.on_add, context.re_stack[-1]) 65 | context.builder.leave() 66 | return 0 67 | 68 | 69 | def out_add(context, path, data=None): 70 | # print "out.add():", path, data 71 | context.builder.add(path, data) 72 | context.builder.enter(path) 73 | context._trigger(context.on_add, context.re_stack[-1]) 74 | context.builder.leave() 75 | return 0 76 | 77 | 78 | def out_add_attribute(context, path, name, value): 79 | # print "out.add_attribute():", path, name, value 80 | context.builder.add_attribute(path, name, value) 81 | context.builder.enter(path) 82 | context._trigger(context.on_add, context.re_stack[-1]) 83 | context.builder.leave() 84 | return 0 85 | 86 | 87 | def out_open(context, path): 88 | # print "out.open():", path 89 | context.builder.open(path) 90 | context._trigger(context.on_add, context.re_stack[-1]) 91 | context.stack[-1].on_leave.append((context.builder.leave, ())) 92 | return 0 93 | 94 | 95 | def out_enter(context, path): 96 | # print "out.enter():", path 97 | context.builder.enter(path) 98 | context._trigger(context.on_add, context.re_stack[-1]) 99 | context.stack[-1].on_leave.append((context.builder.leave, ())) 100 | return 0 101 | 102 | 103 | def out_enqueue_before(context, regex, path, data=None): 104 | # print "ENQ BEFORE", regex.pattern, path, data 105 | context.on_match_before.append((regex, out_add, (context, path, data))) 106 | return 0 107 | 108 | 109 | def out_enqueue_after(context, regex, path, data=None): 110 | # print "ENQ AFTER", regex.pattern, path, data 111 | context.on_match_after.append((regex, out_add, (context, path, data))) 112 | return 0 113 | 114 | 115 | def out_enqueue_on_add(context, regex, path, data=None): 116 | # print "ENQ ON ADD", regex.pattern, path, data 117 | context.on_add.append((regex, out_add, (context, path, data))) 118 | return 0 119 | 120 | 121 | def out_clear_queue(context): 122 | context._clear_triggers() 123 | return 1 124 | 125 | 126 | def out_set_root_name(context, name): 127 | context.builder.set_root_name(name) 128 | return 0 129 | 130 | 131 | class Context(object): 132 | 133 | def __init__(self): 134 | self.functions = {'do.fail': do_fail, 135 | 'do.return': do_return, 136 | 'do.next': do_next, 137 | 'do.skip': do_skip, 138 | 'do.say': do_say, 139 | 'do.warn': do_warn, 140 | 'out.create': out_create, 141 | 'out.replace': out_replace, 142 | 'out.add': out_add, 143 | 'out.add_attribute': out_add_attribute, 144 | 'out.open': out_open, 145 | 'out.enter': out_enter, 146 | 'out.enqueue_before': out_enqueue_before, 147 | 'out.enqueue_after': out_enqueue_after, 148 | 'out.enqueue_on_add': out_enqueue_on_add, 149 | 'out.clear_queue': out_clear_queue, 150 | 'out.set_root_name': out_set_root_name} 151 | self.lexicon = {} 152 | self.grammars = {} 153 | self.input = None 154 | self.builder = None 155 | self.end = 0 156 | self._init() 157 | 158 | def _init(self): 159 | self.start = 0 160 | self.re_stack = [] 161 | self.stack = [] 162 | self._clear_triggers() 163 | 164 | def _clear_triggers(self): 165 | self.on_match_before = [] 166 | self.on_match_after = [] 167 | self.on_add = [] 168 | 169 | def _trigger(self, triggers, match): 170 | matching = [] 171 | for trigger in triggers: 172 | regex, func, args = trigger 173 | if regex.search(match.group(0)) is not None: 174 | matching.append(trigger) 175 | for trigger in matching: 176 | triggers.remove(trigger) 177 | for trigger in matching: 178 | regex, func, args = trigger 179 | func(*args) 180 | 181 | def _match_before_notify(self, match): 182 | self.re_stack.append(match) 183 | self._trigger(self.on_match_before, match) 184 | 185 | def _match_after_notify(self, match): 186 | self._trigger(self.on_match_after, match) 187 | self.re_stack.pop() 188 | 189 | def _get_lineno(self): 190 | return self.input.count('\n', 0, self.start) + 1 191 | 192 | def _get_line(self, number=None): 193 | if number is None: 194 | number = self._get_lineno() 195 | return self.input.split('\n')[number - 1] 196 | 197 | def _get_line_position_from_char(self, char): 198 | line_start = char 199 | while line_start != 0: 200 | if self.input[line_start - 1] == '\n': 201 | break 202 | line_start -= 1 203 | line_end = self.input.find('\n', char) 204 | return line_start, line_end 205 | 206 | def _format(self, error): 207 | start, end = self._get_line_position_from_char(self.start) 208 | line_number = self._get_lineno() 209 | line = self._get_line() 210 | offset = self.start - start 211 | token_len = 1 212 | output = line + '\n' 213 | if token_len <= 1: 214 | output += (' ' * offset) + '^\n' 215 | else: 216 | output += (' ' * offset) + "'" + ('-' * (token_len - 2)) + "'\n" 217 | output += '%s in line %s' % (error, line_number) 218 | return output 219 | 220 | def _msg(self, error): 221 | print(self._format(error)) 222 | 223 | def _warn(self, error): 224 | sys.stderr.write(self._format(error) + '\n') 225 | 226 | def _error(self, error): 227 | raise Exception(self._format(error)) 228 | 229 | def _eof(self): 230 | return self.start >= self.end 231 | 232 | def parse_string(self, input, builder, debug=0): 233 | self._init() 234 | self.input = input 235 | self.builder = builder 236 | self.end = len(input) 237 | self.grammars['input'].parse(self, debug) 238 | if self.start < self.end: 239 | self._error('parser returned, but did not complete') 240 | 241 | def parse(self, filename, builder, encoding='utf8', debug=0): 242 | with codecs.open(filename, 'r', encoding=encoding) as input_file: 243 | return self.parse_string(input_file.read(), builder, debug) 244 | 245 | def dump(self): 246 | for grammar in self.grammars.values(): 247 | print(grammar) 248 | -------------------------------------------------------------------------------- /docs/syntax.rst: -------------------------------------------------------------------------------- 1 | Gelatin Syntax 2 | ============== 3 | 4 | The following functions and types may be used within a Gelatin syntax. 5 | 6 | Types 7 | ----- 8 | 9 | STRING 10 | ^^^^^^ 11 | 12 | A string is any series of characters, delimited by the :code:`'` character. 13 | Escaping is done using the backslash character. Examples:: 14 | 15 | 'test me' 16 | 'test \'escaped\' strings' 17 | 18 | VARNAME 19 | ^^^^^^^ 20 | 21 | VARNAMEs are variable names. They may contain the following set of 22 | characters:: 23 | 24 | [a-z0-9_] 25 | 26 | NODE 27 | ^^^^ 28 | 29 | The output that is generated by Gelatin is represented by a tree 30 | consisting of nodes. The NODE type is used to describe a single node in 31 | a tree. It is a URL notated string consisting of the node name, 32 | optionally followed by attributes. Examples for NODE include: 33 | 34 | :: 35 | 36 | . 37 | element 38 | element?attribute1="foo" 39 | element?attribute1="foo"&attribute2="foo" 40 | 41 | PATH 42 | ^^^^ 43 | 44 | A PATH addresses a node in the tree. Addressing is relative to the 45 | currently selected node. A PATH is a string with the following syntax:: 46 | 47 | NODE[/NODE[/NODE]...]' 48 | 49 | Examples:: 50 | 51 | . 52 | ./child 53 | parent/element?attribute="foo" 54 | parent/child1?name="foo"/child?attribute="foobar" 55 | 56 | REGEX 57 | ^^^^^ 58 | 59 | This type describes a Python regular expression. The expression MUST NOT 60 | extract any subgroups. In other words, when using bracket expressions, 61 | always use ``(?:)``. Example:: 62 | 63 | /^(test|foo|bar)$/ # invalid! 64 | /^(?:test|foo|bar)$/ # valid 65 | 66 | If you are trying to extract a substring, use a match statement with 67 | multiple fields instead. 68 | 69 | Statements 70 | ---------- 71 | 72 | define VARNAME STRING|REGEX|VARNAME 73 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 74 | 75 | `define` statements assign a value to a variable. Examples:: 76 | 77 | define my_test /(?:foo|bar)/ 78 | define my_test2 'foobar' 79 | define my_test3 my_test2 80 | 81 | match STRING|REGEX|VARNAME ... 82 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 83 | 84 | Match statements are lists of tokens that are applied against the 85 | current input document. They parse the input stream by matching at the 86 | current position. On a match, the matching string is consumed from the 87 | input such that the next match statement may be applied. In other words, 88 | the current position in the document is advanced only when a match is 89 | found. 90 | 91 | A match statement must be followed by an indented block. In this block, 92 | each matching token may be accessed using the `$X` variables, where `X` is 93 | the number of the match, starting with `$0`. 94 | 95 | Examples:: 96 | 97 | define digit /[0-9]/ 98 | define number /[0-9]+/ 99 | 100 | grammar input: 101 | match 'foobar': 102 | do.say('Match was: $0!') 103 | match 'foo' 'bar' /[\r\n]/: 104 | do.say('Match was: $0!') 105 | match 'foobar' digit /\s+/ number /[\r\n]/: 106 | do.say('Matches: $1 and $3') 107 | 108 | You may also use multiple matches resulting in a logical OR:: 109 | 110 | match 'foo' '[0-9]' /[\r\n]/ 111 | | 'bar' /[a-z]/ /[\r\n]/ 112 | | 'foobar' /[A-Z]/ /[\r\n]/: 113 | do.say('Match was: $1!') 114 | 115 | imatch STRING|REGEX|VARNAME ... 116 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 117 | 118 | `imatch` statements are like `match` statements, except that matching is 119 | case-insensitive. 120 | 121 | when STRING|REGEX|VARNAME ... 122 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 123 | 124 | `when` statements are like `match` statements, with the difference 125 | that upon a match, the string is not consumed from the input stream. 126 | In other words, the current position in the document is not advanced, 127 | even when a match is found. 128 | `when` statements are generally used in places where you want to "bail 129 | out" of a grammar without consuming the token. 130 | 131 | Example:: 132 | 133 | grammar user: 134 | match 'Name:' /\s+/ /\S+/ /\n/: 135 | do.say('Name was: $2!') 136 | when 'User:': 137 | do.return() 138 | 139 | grammar input: 140 | match 'User:' /\s+/ /\S+/ /\n/: 141 | out.enter('user/name', '$2') 142 | user() 143 | 144 | skip STRING|REGEX|VARNAME 145 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 146 | 147 | `skip` statements are like `match` statements without any actions. 148 | They also do not support lists of tokens, but only one single 149 | expression. 150 | 151 | Example:: 152 | 153 | grammar user: 154 | skip /#.*?[\r\n]+/ 155 | match 'Name: ' /\s+/ /\n/: 156 | do.say('Name was: $2!') 157 | when 'User:': 158 | do.return() 159 | 160 | Output Generating Functions 161 | --------------------------- 162 | 163 | out.create(PATH[, STRING]) 164 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 165 | 166 | Creates the leaf node (and attributes) in the given path, regardless 167 | of whether or not it already exists. In other words, using this 168 | function twice will lead to duplicates. 169 | If the given path contains multiple elements, the parent nodes are 170 | only created if the do not yet exist. 171 | If the STRING argument is given, the new node is also assigned the 172 | string as data. In other words, the following function call:: 173 | 174 | out.create('parent/child?name="test"', 'hello world') 175 | 176 | leads to the following XML output:: 177 | 178 | 179 | hello world 180 | 181 | 182 | Using the same call again, like so:: 183 | 184 | out.create('parent/child?name="test"', 'hello world') 185 | out.create('parent/child?name="test"', 'hello world') 186 | 187 | the resulting XML would look like this:: 188 | 189 | 190 | hello world 191 | hello world 192 | 193 | 194 | out.replace(PATH[, STRING]) 195 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 196 | 197 | Like out.create(), but replaces the nodes in the given path if they 198 | already exist. 199 | 200 | out.add(PATH[, STRING]) 201 | ^^^^^^^^^^^^^^^^^^^^^^^ 202 | 203 | Like out.create(), but appends the string to the text of the existing 204 | node if it already exists. 205 | 206 | out.add_attribute(PATH, NAME, STRING) 207 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 208 | 209 | Adds the attribute with the given name and value to the node with the 210 | given path. 211 | 212 | out.open(PATH[, STRING]) 213 | ^^^^^^^^^^^^^^^^^^^^^^^^ 214 | 215 | Like out.create(), but also selects the addressed node, such that the 216 | PATH of all subsequent function calls is relative to the selected node 217 | until the end of the match block is reached. 218 | 219 | out.enter(PATH[, STRING]) 220 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 221 | 222 | Like out.open(), but only creates the nodes in the given path if they do 223 | not already exist. 224 | 225 | out.enqueue_before(REGEX, PATH[, STRING]) 226 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 227 | 228 | Like out.add(), but is not immediately executed. Instead, it is executed 229 | as soon as the given regular expression matches the input, regardless of 230 | the grammar in which the match occurs. 231 | 232 | out.enqueue_after(REGEX, PATH[, STRING]) 233 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 234 | 235 | Like out.enqueue_before(), but is executed after the given regular 236 | expression matches the input and the next match statement was processed. 237 | 238 | out.enqueue_on_add(REGEX, PATH[, STRING]) 239 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 240 | 241 | Like out.enqueue_before(), but is executed after the given regular 242 | expression matches the input and the next node is added to the output. 243 | 244 | out.clear_queue() 245 | ^^^^^^^^^^^^^^^^^ 246 | 247 | Removes any items from the queue that were previously queued using the 248 | out.enqueue_*() functions. 249 | 250 | out.set_root_name(STRING) 251 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 252 | 253 | Specifies the name of the root tag, if the output is XML. 254 | Has no effect on JSON and YAML output. 255 | 256 | Control Functions 257 | ----------------- 258 | 259 | do.skip() 260 | ^^^^^^^^^ 261 | 262 | Skip the current match and jump back to the top of the current grammar 263 | block. 264 | 265 | do.next() 266 | ^^^^^^^^^ 267 | 268 | | Skip the current match and continue with the next match statement 269 | without jumping back to the top of the current grammar block. 270 | | This function is rarely used and probably not what you want. Instead, 271 | use do.skip() in almost all cases, unless it is for some 272 | performance-specific hacks. 273 | 274 | do.return() 275 | ^^^^^^^^^^^ 276 | 277 | Immediately leave the current grammar block and return to the calling 278 | function. When used at the top level (i.e. in the `input` grammar), stop 279 | parsing. 280 | 281 | do.say(STRING) 282 | ^^^^^^^^^^^^^^ 283 | 284 | Prints the given string to stdout, with additional debug information. 285 | 286 | do.fail(STRING) 287 | ^^^^^^^^^^^^^^^ 288 | 289 | Like do.say(), but immediately terminates with an error. 290 | -------------------------------------------------------------------------------- /Gelatin/generator/Builder.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Samuel Abels 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | import os 21 | import re 22 | import sys 23 | import shutil 24 | from tempfile import NamedTemporaryFile 25 | if sys.version_info[0] > 3 or sys.version_info[0] == 3 and sys.version_info[1] >= 10: 26 | from collections.abc import Callable 27 | else: 28 | from collections import Callable 29 | from collections import OrderedDict, defaultdict 30 | try: 31 | from urllib.parse import urlparse, parse_qs, unquote 32 | except ImportError: 33 | from urlparse import urlparse, unquote 34 | from cgi import parse_qs 35 | 36 | value = r'"(?:\\.|[^"])*"' 37 | attrib = r'(?:[\$\w\-]+=%s)' % value 38 | path_re = re.compile(r'^[^/"\?]+(?:\?%s?(?:&%s?)*)?' % (attrib, attrib)) 39 | 40 | 41 | class OrderedDefaultDict(OrderedDict): 42 | 43 | def __init__(self, default_factory=None, *a, **kw): 44 | if (default_factory is not None and 45 | not isinstance(default_factory, Callable)): 46 | raise TypeError('first argument must be callable') 47 | OrderedDict.__init__(self, *a, **kw) 48 | self.default_factory = default_factory 49 | 50 | def __getitem__(self, key): 51 | try: 52 | return OrderedDict.__getitem__(self, key) 53 | except KeyError: 54 | return self.__missing__(key) 55 | 56 | def __missing__(self, key): 57 | if self.default_factory is None: 58 | raise KeyError(key) 59 | self[key] = value = self.default_factory() 60 | return value 61 | 62 | def __reduce__(self): 63 | if self.default_factory is None: 64 | args = tuple() 65 | else: 66 | args = self.default_factory, 67 | return type(self), args, None, None, self.items() 68 | 69 | def copy(self): 70 | return self.__copy__() 71 | 72 | def __copy__(self): 73 | return type(self)(self.default_factory, self) 74 | 75 | def __deepcopy__(self, memo): 76 | import copy 77 | return type(self)(self.default_factory, 78 | copy.deepcopy(self.items())) 79 | 80 | def __repr__(self): 81 | return 'OrderedDefaultDict(%s, %s)' % (self.default_factory, 82 | OrderedDict.__repr__(self)) 83 | 84 | def nodehash(name, attribs): 85 | return name + '/' + str(hash(frozenset(attribs))) 86 | 87 | class Node(object): 88 | 89 | def __init__(self, name, attribs=None): 90 | self.name = name 91 | self.attribs = attribs and attribs or [] 92 | self.children = OrderedDefaultDict(list) 93 | self.child_index = dict() 94 | self.text = None 95 | 96 | def add(self, child): 97 | self.children[child.name].append(child) 98 | self.child_index[nodehash(child.name, child.attribs)] = child 99 | return child 100 | 101 | def get_child(self, name, attribs=None): 102 | """ 103 | Returns the first child that matches the given name and 104 | attributes. 105 | """ 106 | if name == '.': 107 | if attribs is None or len(attribs) == 0: 108 | return self 109 | if attribs == self.attribs: 110 | return self 111 | return self.child_index.get(nodehash(name, attribs)) 112 | 113 | def to_dict(self): 114 | thedict = OrderedDict(('@' + k, v) for (k, v) in self.attribs) 115 | children_dict = OrderedDict() 116 | for name, child_list in self.children.items(): 117 | if len(child_list) == 1: 118 | children_dict[name] = child_list[0].to_dict() 119 | continue 120 | children_dict[name] = [child.to_dict() for child in child_list] 121 | thedict.update(children_dict) 122 | if self.text is not None: 123 | thedict['#text'] = self.text 124 | return thedict 125 | 126 | def dump(self, indent=0): 127 | for name, children in self.children.items(): 128 | for child in children: 129 | child.dump(indent + 1) 130 | 131 | 132 | class Builder(object): 133 | 134 | """ 135 | Abstract base class for all generators. 136 | """ 137 | 138 | def __init__(self): 139 | self.tree = Node(None) 140 | self.current = [self.tree] 141 | 142 | def serialize(self, serializer): 143 | return serializer.serialize_doc(self.tree) 144 | 145 | def serialize_to_file(self, filename): 146 | with NamedTemporaryFile(delete=False, encoding='utf-8') as thefile: 147 | thefile.write(self.serialize()) 148 | if os.path.exists(filename): 149 | os.unlink(filename) 150 | shutil.move(thefile.name, filename) 151 | 152 | def set_root_name(self, name): 153 | self.tree.name = name 154 | 155 | def dump(self): 156 | # pp = PrettyPrinter(indent = 4) 157 | # pp.pprint(self.tree.to_dict()) 158 | self.tree.dump() 159 | 160 | def _splitpath(self, path): 161 | match = path_re.match(path) 162 | result = [] 163 | while match is not None: 164 | result.append(match.group(0)) 165 | path = path[len(match.group(0)) + 1:] 166 | match = path_re.match(path) 167 | return result 168 | 169 | def _splittag(self, tag): 170 | url = urlparse(tag) 171 | attribs = [] 172 | for key, value in parse_qs(url.query).items(): 173 | value = value[0] 174 | if value.startswith('"') and value.endswith('"'): 175 | value = value[1:-1] 176 | attribs.append((key.lower(), value)) 177 | return url.path.replace(' ', '-').lower(), attribs 178 | 179 | def create(self, path, data=None): 180 | """ 181 | Creates the given node, regardless of whether or not it already 182 | exists. 183 | Returns the new node. 184 | """ 185 | node = self.current[-1] 186 | path = self._splitpath(path) 187 | n_items = len(path) 188 | for n, item in enumerate(path): 189 | tag, attribs = self._splittag(item) 190 | 191 | # The leaf node is always newly created. 192 | if n == n_items-1: 193 | node = node.add(Node(tag, attribs)) 194 | break 195 | 196 | # Parent nodes are only created if they do not exist yet. 197 | existing = node.get_child(tag, attribs) 198 | if existing is not None: 199 | node = existing 200 | else: 201 | node = node.add(Node(tag, attribs)) 202 | if data: 203 | node.text = unquote(data) 204 | return node 205 | 206 | def add(self, path, data=None, replace=False): 207 | """ 208 | Creates the given node if it does not exist. 209 | Returns the (new or existing) node. 210 | """ 211 | node = self.current[-1] 212 | for item in self._splitpath(path): 213 | tag, attribs = self._splittag(item) 214 | next_node = node.get_child(tag, attribs) 215 | if next_node is not None: 216 | node = next_node 217 | else: 218 | node = node.add(Node(tag, attribs)) 219 | if replace: 220 | node.text = '' 221 | if data: 222 | if node.text is None: 223 | node.text = unquote(data) 224 | else: 225 | node.text += unquote(data) 226 | return node 227 | 228 | def add_attribute(self, path, name, value): 229 | """ 230 | Creates the given attribute and sets it to the given value. 231 | Returns the (new or existing) node to which the attribute was added. 232 | """ 233 | node = self.add(path) 234 | node.attribs.append((name, value)) 235 | return node 236 | 237 | def open(self, path): 238 | """ 239 | Creates and enters the given node, regardless of whether it already 240 | exists. 241 | Returns the new node. 242 | """ 243 | self.current.append(self.create(path)) 244 | return self.current[-1] 245 | 246 | def enter(self, path): 247 | """ 248 | Enters the given node. Creates it if it does not exist. 249 | Returns the node. 250 | """ 251 | self.current.append(self.add(path)) 252 | return self.current[-1] 253 | 254 | def leave(self): 255 | """ 256 | Returns to the node that was selected before the last call to enter(). 257 | The history is a stack, to the method may be called multiple times. 258 | """ 259 | self.current.pop() 260 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Gelatin documentation build configuration file, created by 4 | # sphinx-quickstart on Tue Sep 4 14:21:46 2012. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | sys.path.insert(0, os.path.abspath('..')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | #extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.mathjax', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode'] 29 | extensions = ['sphinx.ext.autodoc', # 'sphinx.ext.coverage', 30 | 'sphinx.ext.viewcode', 31 | 'sphinx.ext.autosummary', 32 | #'sphinx.ext.intersphinx', 33 | ] 34 | 35 | # Add any paths that contain templates here, relative to this directory. 36 | templates_path = ['_templates'] 37 | 38 | # The suffix of source filenames. 39 | source_suffix = '.rst' 40 | 41 | # The encoding of source files. 42 | #source_encoding = 'utf-8-sig' 43 | 44 | # The master toctree document. 45 | master_doc = 'index' 46 | 47 | # General information about the project. 48 | project = u'Gelatin' 49 | copyright = u'2017, Samuel Abels' 50 | 51 | # The version info for the project you're documenting, acts as replacement for 52 | # |version| and |release|, also used in various other places throughout the 53 | # built documents. 54 | # 55 | # The short X.Y version. 56 | version = '0.3' 57 | # The full version, including alpha/beta/rc tags. 58 | release = '0.3.4' 59 | 60 | # The language for content autogenerated by Sphinx. Refer to documentation 61 | # for a list of supported languages. 62 | #language = None 63 | 64 | # There are two options for replacing |today|: either, you set today to some 65 | # non-false value, then it is used: 66 | #today = '' 67 | # Else, today_fmt is used as the format for a strftime call. 68 | #today_fmt = '%B %d, %Y' 69 | 70 | # List of patterns, relative to source directory, that match files and 71 | # directories to ignore when looking for source files. 72 | exclude_patterns = ['_build'] 73 | 74 | # The reST default role (used for this markup: `text`) to use for all documents. 75 | #default_role = None 76 | 77 | # If true, '()' will be appended to :func: etc. cross-reference text. 78 | #add_function_parentheses = True 79 | 80 | # If true, the current module name will be prepended to all description 81 | # unit titles (such as .. function::). 82 | #add_module_names = True 83 | 84 | # If true, sectionauthor and moduleauthor directives will be shown in the 85 | # output. They are ignored by default. 86 | #show_authors = False 87 | 88 | # The name of the Pygments (syntax highlighting) style to use. 89 | pygments_style = 'sphinx' 90 | 91 | # A list of ignored prefixes for module index sorting. 92 | #modindex_common_prefix = [] 93 | 94 | 95 | # -- Options for HTML output --------------------------------------------------- 96 | 97 | # The theme to use for HTML and HTML Help pages. See the documentation for 98 | # a list of builtin themes. 99 | html_theme = 'default' 100 | 101 | # Theme options are theme-specific and customize the look and feel of a theme 102 | # further. For a list of options available for each theme, see the 103 | # documentation. 104 | #html_theme_options = {} 105 | 106 | # Add any paths that contain custom themes here, relative to this directory. 107 | #html_theme_path = [] 108 | 109 | # The name for this set of Sphinx documents. If None, it defaults to 110 | # " v documentation". 111 | #html_title = None 112 | 113 | # A shorter title for the navigation bar. Default is the same as html_title. 114 | #html_short_title = None 115 | 116 | # The name of an image file (relative to this directory) to place at the top 117 | # of the sidebar. 118 | #html_logo = None 119 | 120 | # The name of an image file (within the static path) to use as favicon of the 121 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 122 | # pixels large. 123 | #html_favicon = None 124 | 125 | # Add any paths that contain custom static files (such as style sheets) here, 126 | # relative to this directory. They are copied after the builtin static files, 127 | # so a file named "default.css" will overwrite the builtin "default.css". 128 | html_static_path = ['_static'] 129 | 130 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 131 | # using the given strftime format. 132 | #html_last_updated_fmt = '%b %d, %Y' 133 | 134 | # If true, SmartyPants will be used to convert quotes and dashes to 135 | # typographically correct entities. 136 | #html_use_smartypants = True 137 | 138 | # Custom sidebar templates, maps document names to template names. 139 | #html_sidebars = {} 140 | 141 | # Additional templates that should be rendered to pages, maps page names to 142 | # template names. 143 | #html_additional_pages = {} 144 | 145 | # If false, no module index is generated. 146 | #html_domain_indices = True 147 | 148 | # If false, no index is generated. 149 | #html_use_index = True 150 | 151 | # If true, the index is split into individual pages for each letter. 152 | #html_split_index = False 153 | 154 | # If true, links to the reST sources are added to the pages. 155 | #html_show_sourcelink = True 156 | 157 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 158 | #html_show_sphinx = True 159 | 160 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 161 | #html_show_copyright = True 162 | 163 | # If true, an OpenSearch description file will be output, and all pages will 164 | # contain a tag referring to it. The value of this option must be the 165 | # base URL from which the finished HTML is served. 166 | #html_use_opensearch = '' 167 | 168 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 169 | #html_file_suffix = None 170 | 171 | # Output file base name for HTML help builder. 172 | htmlhelp_basename = 'Gelatin' 173 | 174 | 175 | # -- Options for LaTeX output -------------------------------------------------- 176 | 177 | latex_elements = { 178 | # The paper size ('letterpaper' or 'a4paper'). 179 | #'papersize': 'letterpaper', 180 | 181 | # The font size ('10pt', '11pt' or '12pt'). 182 | #'pointsize': '10pt', 183 | 184 | # Additional stuff for the LaTeX preamble. 185 | #'preamble': '', 186 | } 187 | 188 | # Grouping the document tree into LaTeX files. List of tuples 189 | # (source start file, target name, title, author, documentclass [howto/manual]). 190 | latex_documents = [ 191 | ('index', 'Gelatin.tex', u'Gelatin Documentation', 192 | u'Samuel Abels', 'manual'), 193 | ] 194 | 195 | # The name of an image file (relative to this directory) to place at the top of 196 | # the title page. 197 | #latex_logo = None 198 | 199 | # For "manual" documents, if this is true, then toplevel headings are parts, 200 | # not chapters. 201 | #latex_use_parts = False 202 | 203 | # If true, show page references after internal links. 204 | #latex_show_pagerefs = False 205 | 206 | # If true, show URL addresses after external links. 207 | #latex_show_urls = False 208 | 209 | # Documents to append as an appendix to all manuals. 210 | #latex_appendices = [] 211 | 212 | # If false, no module index is generated. 213 | #latex_domain_indices = True 214 | 215 | 216 | # -- Options for manual page output -------------------------------------------- 217 | 218 | # One entry per manual page. List of tuples 219 | # (source start file, name, description, authors, manual section). 220 | man_pages = [ 221 | ('index', 'gelatin', u'Gelatin Documentation', 222 | [u'Samuel Abels'], 1) 223 | ] 224 | 225 | # If true, show URL addresses after external links. 226 | #man_show_urls = False 227 | 228 | 229 | # -- Options for Texinfo output ------------------------------------------------ 230 | 231 | # Grouping the document tree into Texinfo files. List of tuples 232 | # (source start file, target name, title, author, 233 | # dir menu entry, description, category) 234 | texinfo_documents = [ 235 | ('index', 'Gelatin', u'Gelatin Documentation', 236 | u'Samuel Abels', 'Gelatin', 'One line description of project.', 237 | 'Miscellaneous'), 238 | ] 239 | 240 | # Documents to append as an appendix to all manuals. 241 | #texinfo_appendices = [] 242 | 243 | # If false, no module index is generated. 244 | #texinfo_domain_indices = True 245 | 246 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 247 | #texinfo_show_urls = 'footnote' 248 | 249 | 250 | # -- Options for Epub output --------------------------------------------------- 251 | 252 | # Bibliographic Dublin Core info. 253 | epub_title = u'Gelatin' 254 | epub_author = u'Samuel Abels' 255 | epub_publisher = u'Samuel Abels' 256 | epub_copyright = u'2017, Samuel Abels' 257 | 258 | # The language of the text. It defaults to the language option 259 | # or en if the language is not set. 260 | #epub_language = '' 261 | 262 | # The scheme of the identifier. Typical schemes are ISBN or URL. 263 | #epub_scheme = '' 264 | 265 | # The unique identifier of the text. This can be a ISBN number 266 | # or the project homepage. 267 | #epub_identifier = '' 268 | 269 | # A unique identification for the text. 270 | #epub_uid = '' 271 | 272 | # A tuple containing the cover image and cover page html template filenames. 273 | #epub_cover = () 274 | 275 | # HTML files that should be inserted before the pages created by sphinx. 276 | # The format is a list of tuples containing the path and title. 277 | #epub_pre_files = [] 278 | 279 | # HTML files shat should be inserted after the pages created by sphinx. 280 | # The format is a list of tuples containing the path and title. 281 | #epub_post_files = [] 282 | 283 | # A list of files that should not be packed into the epub file. 284 | #epub_exclude_files = [] 285 | 286 | # The depth of the table of contents in toc.ncx. 287 | #epub_tocdepth = 3 288 | 289 | # Allow duplicate toc entries. 290 | #epub_tocdup = True 291 | 292 | 293 | # Example configuration for intersphinx: refer to the Python standard library. 294 | intersphinx_mapping = {'http://docs.python.org/': None} 295 | 296 | def skip(app, what, name, obj, skip, options): 297 | if name == "__init__": 298 | return False 299 | return skip 300 | 301 | def setup(app): 302 | app.connect("autodoc-skip-member", skip) 303 | --------------------------------------------------------------------------------