# HG changeset patch # User Peter Sanchez # Date 1603157694 25200 # Mon Oct 19 18:34:54 2020 -0700 # Node ID 64f694b06df5c2f5140d456de6304804d4d8c8be # Parent 0000000000000000000000000000000000000000 Initial commit diff --git a/.hgignore b/.hgignore new file mode 100644 --- /dev/null +++ b/.hgignore @@ -0,0 +1,9 @@ +syntax:glob +.*.swp +**.pyc +*.*~ +.coverage +.coveragerc +dist/ +*.egg-info/ +*.egg diff --git a/LICENSE b/LICENSE new file mode 100644 --- /dev/null +++ b/LICENSE @@ -0,0 +1,32 @@ +Copyright (c) 2009, Peter Sanchez +All rights reserved. + +Redistribution and use in source and binary forms, with or +without modification, are permitted provided that the +following conditions are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + * Neither the name of Peter Sanchez nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +# py-scfg + +A Python module to parse [scfg][] formatted configuration files. + +It's a simple port of the Go module [go-scfg][]. + +**Note:** Not to be confused with [pyscfg][] which appears to parse +configurations into and out of YAML files. + +## Usage + +Very simple: + + import scfg + + conf = scfg.Config("~/your_config.conf") + conf.load() + train = conf.get("train") + train_models = train.get_all("model") + +## Contributing + +We accept patches submitted via `hg email` which is the `patchbomb` extension +included with Mercurial. + +Please see our [contributing document][cdoc] for more information. + +The mailing list where you submit your patches is +`~petersanchez/public-inbox@lists.code.netlandish.com`. You can also view the +archives on the web here: + +https://lists.code.netlandish.com/~petersanchez/public-inbox + +To quickly setup your clone of `py-scfg` to submit to the mailing +list just edit your `.hg/hgrc` file and add the following: + + [email] + to = ~petersanchez/public-inbox@lists.code.netlandish.com + + [patchbomb] + flagtemplate = "py-scfg" + + [diff] + git = 1 + +We have more information on the topic here: + +- [Contributing][cdoc] +- [Using email with Mercurial][hgemail] +- [Mailing list etiquette][etiquette] + +[etiquette]: https://man.code.netlandish.com/lists/etiquette.md +[hgemail]: https://man.code.netlandish.com/hg/email.md +[cdoc]: https://man.code.netlandish.com/contributing.md + +## Copyright & License + +All documentation, libraries, and sample code are Copyright Peter +Sanchez <>. The library and sample code +are made available to you under the terms of the BSD license which is +contained in the included file, `LICENSE`. + +## Commercial Support + +This software, and lots of other software like it, has been built in +support of many of Netlandish's own projects, and the projects of our +clients. We would love to help you on your next project so get in touch +by dropping us a note at . + +[scfg]: https://git.sr.ht/~emersion/scfg +[go-scfg]: https://git.sr.ht/~emersion/go-scfg +[pyscfg]: https://pypi.org/project/pyscfg/ diff --git a/README.rst b/README.rst new file mode 100644 --- /dev/null +++ b/README.rst @@ -0,0 +1,80 @@ +py-scfg +======= + +A Python module to parse `scfg `__ +formatted configuration files. + +It's a simple port of the Go module +`go-scfg `__. + +**Note:** Not to be confused with +`pyscfg `__ which appears to parse +configurations into and out of YAML files. + +Usage +----- + +Very simple: + +:: + + import scfg + + conf = scfg.Config("~/your_config.conf") + conf.load() + train = conf.get("train") + train_models = train.get_all("model") + +Contributing +------------ + +We accept patches submitted via ``hg email`` which is the ``patchbomb`` +extension included with Mercurial. + +Please see our `contributing +document `__ for more +information. + +The mailing list where you submit your patches is +``~petersanchez/public-inbox@lists.code.netlandish.com``. You can also +view the archives on the web here: + +https://lists.code.netlandish.com/~petersanchez/public-inbox + +To quickly setup your clone of ``py-scfg`` to submit to the mailing list +just edit your ``.hg/hgrc`` file and add the following: + +:: + + [email] + to = ~petersanchez/public-inbox@lists.code.netlandish.com + + [patchbomb] + flagtemplate = "py-scfg" + + [diff] + git = 1 + +We have more information on the topic here: + +- `Contributing `__ +- `Using email with + Mercurial `__ +- `Mailing list + etiquette `__ + +Copyright & License +------------------- + +All documentation, libraries, and sample code are Copyright Peter +Sanchez . The library and sample code are made +available to you under the terms of the BSD license which is contained +in the included file, ``LICENSE``. + +Commercial Support +------------------ + +This software, and lots of other software like it, has been built in +support of many of Netlandish's own projects, and the projects of our +clients. We would love to help you on your next project so get in touch +by dropping us a note at hello@netlandish.com. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,29 @@ +[tool.black] +line-length = 79 +target-version = ['py37', 'py38'] +include = '\.pyi?$' +exclude = ''' + +( + /( + \.eggs # exclude a few common directories in the + | \.git # root of the project + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | env + | _build + | buck-out + | build + | dist + )/ + | foo.py # also separately exclude a file named foo.py in + # the root of the project +) +''' + +[tool.isort] +line_length=79 +multi_line_output=5 +include_trailing_comma=true diff --git a/scfg/__init__.py b/scfg/__init__.py new file mode 100644 --- /dev/null +++ b/scfg/__init__.py @@ -0,0 +1,92 @@ +import shlex + + +def get(directives, name): + for d in directives: + if d.name == name: + return d + + +def get_all(directives, name): + results = [] + for d in directives: + if d.name == name: + results.append(d) + return results + + +class Directive: + name = None + params = None + children = None + + def __init__(self, name, params, children=None): + self.name = name + self.params = params + if children is not None: + self.children = children + + def __str__(self): + return f"{self.name}: {self.params}" + + def get(self, name): + return get(self.children, name) + + def get_all(self, name): + return get_all(self.children, name) + + +class Config: + directives = None + + def __init__(self, filename): + self.filename = filename + + def load(self): + # Let Python raise the exception if there's an issue with filename + with open(self.filename) as fp: + directives, closing_brace = self.read_block(fp) + self.directives = directives + + def read_block(self, fp): + blocks = [] + closing_brace = False + + for line in fp: + line = line.strip() + if line.startswith("#"): + continue + + words = shlex.split(line) + if not len(words): + continue + + if len(words) == 1 and line[-1] == "}": + closing_brace = True + break + + if words[-1] == "{" and line[-1] == "{": + words = words[:-1] + + name = "" + params = words + + if len(words) > 0: + name, params = words[0], words[1:] + + child_block, child_closing_brace = self.read_block(fp) + if not child_closing_brace: + raise ValueError("Unexpected EOF") + d = Directive(name, params, child_block) + else: + d = Directive(words[0], words[1:]) + + blocks.append(d) + + return blocks, closing_brace + + def get(self, name): + return get(self.directives, name) + + def get_all(self, name): + return get_all(self.directives, name) diff --git a/setup.py b/setup.py new file mode 100644 --- /dev/null +++ b/setup.py @@ -0,0 +1,34 @@ +import os + +from setuptools import find_packages, setup + +if os.path.exists("README.rst"): + long_description = open("README.rst", "r").read() +else: + long_description = ( + "See https://hg.code.netlandish.com/~petersanchez/py-scfg" + ) + + +setup( + name="py-scfg", + version="0.1.0", + packages=find_packages(), + description="Module to read scfg formatted configuration files.", + author="Peter Sanchez", + author_email="pjs@petersanchez.com", + url="https://hg.code.netlandish.com/~petersanchez/py-scfg", + long_description=long_description, + platforms=["any"], + classifiers=[ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + ], + include_package_data=True, +) diff --git a/test.conf b/test.conf new file mode 100644 --- /dev/null +++ b/test.conf @@ -0,0 +1,16 @@ +train "Shinkansen" { + model "E5" { + max-speed 320km/h + # this is a test + weight 453.5t + + lines-served "Tōhoku" "Hokkaido" + } + + model "E7" { + max-speed 275km/h + weight 540t + + lines-served "Hokuriku" "Jōetsu" + } +} diff --git a/tests.py b/tests.py new file mode 100644 --- /dev/null +++ b/tests.py @@ -0,0 +1,31 @@ +import unittest + +import scfg + + +class SCFGTest(unittest.TestCase): + def setUp(self): + self.config = scfg.Config("test.conf") + self.config.load() + + def test_parse(self): + self.assertEqual(len(self.config.directives), 1) + self.assertEqual(len(self.config.directives[0].children), 2) + for d in self.config.directives[0].children: + self.assertEqual(len(d.children), 3) + + def test_values(self): + train = self.config.get("train") + self.assertEqual(train.name, "train") + + self.assertEqual(len(train.get_all("model")), 2) + self.assertEqual( + train.get_all("model")[0].get("lines-served").params[1], "Hokkaido" + ) + self.assertEqual( + train.get_all("model")[1].get("weight").params[0], "540t" + ) + + +if __name__ == "__main__": + unittest.main()