9 files changed, 395 insertions(+), 0 deletions(-)

A => .hgignore
A => LICENSE
A => README.md
A => README.rst
A => pyproject.toml
A => scfg/__init__.py
A => setup.py
A => test.conf
A => tests.py
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 &lt;<pjs@petersanchez.com>&gt;. 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()