A => .hgignore +9 -0
@@ 0,0 1,9 @@
+syntax:glob
+.*.swp
+**.pyc
+*.*~
+.coverage
+.coveragerc
+dist/
+*.egg-info/
+*.egg
A => LICENSE +32 -0
@@ 0,0 1,32 @@
+Copyright (c) 2009, Peter Sanchez <pjs@petersanchez.com>
+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.
A => README.md +72 -0
@@ 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 <<pjs@petersanchez.com>>. 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>.
+
+[scfg]: https://git.sr.ht/~emersion/scfg
+[go-scfg]: https://git.sr.ht/~emersion/go-scfg
+[pyscfg]: https://pypi.org/project/pyscfg/
A => README.rst +80 -0
@@ 0,0 1,80 @@
+py-scfg
+=======
+
+A Python module to parse `scfg <https://git.sr.ht/~emersion/scfg>`__
+formatted configuration files.
+
+It's a simple port of the Go module
+`go-scfg <https://git.sr.ht/~emersion/go-scfg>`__.
+
+**Note:** Not to be confused with
+`pyscfg <https://pypi.org/project/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 <https://man.code.netlandish.com/contributing.md>`__ 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 <https://man.code.netlandish.com/contributing.md>`__
+- `Using email with
+ Mercurial <https://man.code.netlandish.com/hg/email.md>`__
+- `Mailing list
+ etiquette <https://man.code.netlandish.com/lists/etiquette.md>`__
+
+Copyright & License
+-------------------
+
+All documentation, libraries, and sample code are Copyright Peter
+Sanchez <pjs@petersanchez.com>. 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.
A => pyproject.toml +29 -0
@@ 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
A => scfg/__init__.py +92 -0
@@ 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)
A => setup.py +34 -0
@@ 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,
+)
A => test.conf +16 -0
@@ 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"
+ }
+}
A => tests.py +31 -0
@@ 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()