Skip to content

Commit cc7ab45

Browse files
committed
Merge branch 'packaging'
2 parents 78146c0 + a2680ed commit cc7ab45

16 files changed

+451
-148
lines changed

.github/workflows/ci.yml

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
name: CI
22
on: [push, pull_request]
33

4+
env:
5+
HSD_PYTHON_VERSION: '0.1'
6+
47
jobs:
5-
test-new:
8+
test:
69

710
runs-on: ubuntu-latest
811

@@ -15,10 +18,16 @@ jobs:
1518
python-version: '3.x'
1619

1720
- name: Install requirements (PIP)
18-
run: pip3 install pytest sphinx numpy
21+
run: pip3 install pytest sphinx numpy build
22+
23+
- name: Setup up root directory
24+
run: echo "PACKAGE_ROOT=${PWD}/src" >> $GITHUB_ENV
1925

20-
- name: Setup up PYTHONPATH
21-
run: echo "PYTHONPATH=${PWD}/src" >> $GITHUB_ENV
26+
- name: Build and install package
27+
run: |
28+
python -m build
29+
pip install dist/hsd_python*.whl
30+
python -c "import hsd; assert hsd.__version__ == '${HSD_PYTHON_VERSION}'"
2231
2332
- name: Run test pytest
2433
run: python3 -m pytest

README.rst

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
HSD — Make your structured data human friendly
33
**********************************************
44

5-
This package contains utilities to read and write files in the Human-friendly
6-
Structured Data (HSD) format.
5+
Utilities to read and write files in the Human-friendly Structured Data (HSD)
6+
format.
77

88
The HSD-format is very similar to both JSON and YAML, but tries to minimize the
99
effort for **humans** to read and write it. It ommits special characters as much
@@ -22,7 +22,7 @@ Installation
2222

2323
The package can be installed via conda-forge::
2424

25-
conda install hsd-python
25+
conda install --channel "conda-forge" hsd-python
2626

2727
Alternatively, the package can be downloaded and installed via pip into the
2828
active Python interpreter (preferably using a virtual python environment) by ::

devtools/set_version

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/usr/bin/env python3
2+
3+
"""Sets a version number in all relevant project files"""
4+
5+
import sys
6+
import re
7+
import os
8+
9+
# The pattern the version number must satisfy
10+
VERSION_PATTERN = r'\d+\.\d+(?:\.\d+)?(?:-\w+)?'
11+
12+
# List of (file name, search pattern, replacement pattern) tuples for all
13+
# the occurancies to be replaced.
14+
FILES_PATTERNS = [('src/hsd/__init__.py',
15+
r'^__version__\s*=\s*([\'"]){}\1'.format(VERSION_PATTERN),
16+
"__version__ = '{version}'"),
17+
('docs/introduction.rst',
18+
r'hsd-python version[ ]*{}.'.format(VERSION_PATTERN),
19+
'hsd-python version {shortversion}.'),
20+
('setup.cfg',
21+
r'version\s*=\s*{}'.format(VERSION_PATTERN),
22+
"version = {version}"),
23+
('docs/conf.py',
24+
r'release\s*=\s*([\'"]){}\1'.format(VERSION_PATTERN),
25+
"release = '{version}'"),
26+
('.github/workflows/ci.yml',
27+
r'HSD_PYTHON_VERSION:\s*([\'"]){}\1'.format(VERSION_PATTERN),
28+
"HSD_PYTHON_VERSION: '{version}'"),
29+
]
30+
31+
32+
def main():
33+
"""Main script."""
34+
35+
if len(sys.argv) < 2:
36+
sys.stderr.write("Missing version string\n")
37+
sys.exit(1)
38+
39+
version, shortversion = _get_version_strings(sys.argv[1])
40+
rootdir = os.path.join(os.path.dirname(sys.argv[0]), '..')
41+
_replace_version_in_files(FILES_PATTERNS, rootdir, version, shortversion)
42+
_replace_version_in_changelog(rootdir, version)
43+
44+
45+
def _get_version_strings(version):
46+
"""Returns version and the short version as string"""
47+
48+
match = re.match(VERSION_PATTERN, version)
49+
if match is None:
50+
print("Invalid version string")
51+
sys.exit(1)
52+
53+
shortversion = '.'.join(version.split('.')[0:2])
54+
return version, shortversion
55+
56+
57+
def _replace_version_in_files(files_patterns, rootdir, version, shortversion):
58+
"""Replaces version number in given files with given search/replacement patterns"""
59+
60+
for fname, regexp, repl in files_patterns:
61+
fname = os.path.join(rootdir, fname)
62+
print("Replacments in '{}': ".format(os.path.relpath(fname, rootdir)), end='')
63+
fp = open(fname, 'r')
64+
txt = fp.read()
65+
fp.close()
66+
replacement = repl.format(version=version, shortversion=shortversion)
67+
newtxt, nsub = re.subn(regexp, replacement, txt, flags=re.MULTILINE)
68+
print(nsub)
69+
fp = open(fname, 'w')
70+
fp.write(newtxt)
71+
fp.close()
72+
73+
74+
def _replace_version_in_changelog(rootdir, version):
75+
"""Replaces the unreleased section in CHANGELOG.rst"""
76+
77+
fname = os.path.join(rootdir, 'CHANGELOG.rst')
78+
print("Replacments in '{}': ".format(os.path.relpath(fname, rootdir)), end='')
79+
fp = open(fname, 'r')
80+
txt = fp.read()
81+
fp.close()
82+
decoration = '=' * len(version)
83+
newtxt, nsub = re.subn(
84+
r'^Unreleased\s*\n=+', version + r'\n' + decoration, txt,
85+
count=1, flags=re.MULTILINE)
86+
print(nsub)
87+
fp = open(fname, 'w')
88+
fp.write(newtxt)
89+
fp.close()
90+
91+
92+
if __name__ == '__main__':
93+
main()

docs/hsd.rst

+8-4
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ nodes will be mapped to one key, which will contain a list of dictionaries
162162
]
163163
},
164164
]
165+
# Also attributes becomes a list. Due to technialc reasons the
166+
# dictbuilder always creates an attribute list for mulitple nodes,
167+
# even if none of the nodes carries an actual attribute.
168+
"PointCharges.attrib": [None, None]
165169
}
166170
}
167171

@@ -186,7 +190,7 @@ to record following additional data for each HSD node:
186190

187191
If this information is being recorded, a special key with the
188192
``.hsdattrib`` suffix will be generated for each node in the dictionary/JSON
189-
presentation. The correpsonding value will be a dictionary with those
193+
presentation. The corresponding value will be a dictionary with those
190194
information.
191195

192196
As an example, let's store the input from the previous section ::
@@ -199,7 +203,7 @@ As an example, let's store the input from the previous section ::
199203
}
200204

201205
in the file `test.hsd`, parse it and convert the node names to lower case
202-
(to make the input processing case-insensitive). Using the Python command ::
206+
(to make enable case-insensitive input processing). Using the Python command ::
203207

204208
inpdict = hsd.load("test.hsd", lower_tag_names=True, include_hsd_attribs=True)
205209

@@ -208,13 +212,13 @@ will yield the following dictionary representation of the input::
208212
{
209213
'hamiltonian.hsdattrib': {'equal': True, 'line': 0, 'tag': 'Hamiltonian'},
210214
'hamiltonian': {
211-
'dftb.hsdattrib': {'line': 0, 'tag': 'Dftb'},
215+
'dftb.hsdattrib': {'line': 0, equal: False, 'tag': 'Dftb'},
212216
'dftb': {
213217
'scc.hsdattrib': {'equal': True, 'line': 1, 'tag': 'Scc'},
214218
'scc': True,
215219
'filling.hsdattrib': {'equal': True, 'line': 2, 'tag': 'Filling'},
216220
'filling': {
217-
'fermi.hsdattrib': {'line': 2, 'tag': 'Fermi'},
221+
'fermi.hsdattrib': {'line': 2, 'equal': False, 'tag': 'Fermi'},
218222
'fermi': {
219223
'temperature.attrib': 'Kelvin',
220224
'temperature.hsdattrib': {'equal': True, 'line': 3,

docs/introduction.rst

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ scientific simulation tool (`DFTB+ <https://github.com/dftbplus/dftbplus>`_),
1313
but is of general purpose. Data stored in HSD can be easily mapped to a subset
1414
of JSON, YAML or XML and *vice versa*.
1515

16+
This document describes hsd-python version 0.1.
17+
1618

1719
Installation
1820
============

src/hsd/__init__.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
#------------------------------------------------------------------------------#
2-
# hsd-python: package for manipulating HSD-formatted data in Python #
3-
# Copyright (C) 2011 - 2021 DFTB+ developers group #
4-
# Licensed under the BSD 2-clause license. #
5-
#------------------------------------------------------------------------------#
1+
#--------------------------------------------------------------------------------------------------#
2+
# hsd-python: package for manipulating HSD-formatted data in Python #
3+
# Copyright (C) 2011 - 2021 DFTB+ developers group #
4+
# Licensed under the BSD 2-clause license. #
5+
#--------------------------------------------------------------------------------------------------#
66
#
77
"""
88
Toolbox for reading, writing and manipulating HSD-data.
@@ -14,3 +14,5 @@
1414
from hsd.formatter import HsdFormatter
1515
from hsd.io import load, load_string, dump, dump_string
1616
from hsd.parser import HsdParser
17+
18+
__version__ = '0.1'

src/hsd/common.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
#------------------------------------------------------------------------------#
2-
# hsd-python: package for manipulating HSD-formatted data in Python #
3-
# Copyright (C) 2011 - 2021 DFTB+ developers group #
4-
# Licensed under the BSD 2-clause license. #
5-
#------------------------------------------------------------------------------#
1+
#--------------------------------------------------------------------------------------------------#
2+
# hsd-python: package for manipulating HSD-formatted data in Python #
3+
# Copyright (C) 2011 - 2021 DFTB+ developers group #
4+
# Licensed under the BSD 2-clause license. #
5+
#--------------------------------------------------------------------------------------------------#
66
#
77
"""
88
Implements common functionalities for the HSD package

src/hsd/dict.py

+59-38
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
#------------------------------------------------------------------------------#
2-
# hsd-python: package for manipulating HSD-formatted data in Python #
3-
# Copyright (C) 2011 - 2021 DFTB+ developers group #
4-
# Licensed under the BSD 2-clause license. #
5-
#------------------------------------------------------------------------------#
1+
#--------------------------------------------------------------------------------------------------#
2+
# hsd-python: package for manipulating HSD-formatted data in Python #
3+
# Copyright (C) 2011 - 2021 DFTB+ developers group #
4+
# Licensed under the BSD 2-clause license. #
5+
#--------------------------------------------------------------------------------------------------#
66
#
77
"""
88
Contains an event-driven builder for dictionary based (JSON-like) structure
99
"""
1010
import re
1111
from typing import List, Tuple, Union
12-
from hsd.common import np, ATTRIB_SUFFIX, HSD_ATTRIB_SUFFIX, HsdError,\
12+
from hsd.common import HSD_ATTRIB_NAME, np, ATTRIB_SUFFIX, HSD_ATTRIB_SUFFIX, HsdError,\
1313
QUOTING_CHARS, SPECIAL_CHARS
1414
from hsd.eventhandler import HsdEventHandler, HsdEventPrinter
1515

@@ -42,14 +42,16 @@ class HsdDictBuilder(HsdEventHandler):
4242
4343
Args:
4444
flatten_data: Whether multiline data in the HSD input should be
45-
flattened into a single list. Othewise a list of lists is created,
46-
with one list for every line (default).
47-
include_hsd_attribs: Whether the HSD-attributes (processing related
48-
attributes, like original tag name, line information, etc.) should
49-
be stored.
45+
flattened into a single list. Othewise a list of lists is created, with one list for
46+
every line (default).
47+
lower_tag_names: Whether tag names should be all converted to lower case (to ease case
48+
insensitive processing). Default: False. If set and include_hsd_attribs is also set,
49+
the original tag names can be retrieved from the "name" hsd attributes.
50+
include_hsd_attribs: Whether the HSD-attributes (processing related attributes, like
51+
original tag name, line information, etc.) should be stored (default: False).
5052
"""
5153

52-
def __init__(self, flatten_data: bool = False,
54+
def __init__(self, flatten_data: bool = False, lower_tag_names: bool = False,
5355
include_hsd_attribs: bool = False):
5456
super().__init__()
5557
self._hsddict: dict = {}
@@ -58,6 +60,7 @@ def __init__(self, flatten_data: bool = False,
5860
self._data: Union[None, _DataType] = None
5961
self._attribs: List[Tuple[str, dict]] = []
6062
self._flatten_data: bool = flatten_data
63+
self._lower_tag_names: bool = lower_tag_names
6164
self._include_hsd_attribs: bool = include_hsd_attribs
6265

6366

@@ -79,35 +82,49 @@ def open_tag(self, tagname, attrib, hsdattrib):
7982
def close_tag(self, tagname):
8083
attrib, hsdattrib = self._attribs.pop(-1)
8184
parentblock = self._parentblocks.pop(-1)
85+
key = tagname.lower() if self._lower_tag_names else tagname
8286
prevcont = parentblock.get(tagname)
83-
if prevcont is not None:
84-
if isinstance(prevcont, dict) and self._data is None:
85-
prevcont = [prevcont]
86-
parentblock[tagname] = prevcont
87-
elif not (isinstance(prevcont, list)
88-
and isinstance(prevcont[0], dict)):
89-
msg = f"Invalid duplicate occurance of node '{tagname}'"
90-
raise HsdError(msg)
91-
92-
if prevcont is None:
93-
content = self._data if self._data is not None else self._curblock
94-
parentblock[tagname] = content
95-
if attrib:
96-
parentblock[tagname + ATTRIB_SUFFIX] = attrib
97-
if self._include_hsd_attribs:
98-
parentblock[tagname + HSD_ATTRIB_SUFFIX] = hsdattrib
87+
88+
if self._data is not None:
89+
if prevcont is None:
90+
parentblock[key] = self._data
91+
elif isinstance(prevcont, list) and len(prevcont) > 0 and isinstance(prevcont[0], dict):
92+
prevcont.append({None: self._data})
93+
elif isinstance(prevcont, dict):
94+
parentblock[key] = [prevcont, {None: self._data}]
95+
else:
96+
parentblock[key] = [{None: prevcont}, {None: self._data}]
9997
else:
100-
prevcont.append(self._curblock)
101-
prevattrib = parentblock.get(tagname + ATTRIB_SUFFIX)
102-
if not (prevattrib is None and attrib is None):
103-
msg = f"Duplicate node '{tagname}' should not carry attributes"
104-
if self._include_hsd_attribs:
105-
prevhsdattrib = parentblock.get(tagname + HSD_ATTRIB_SUFFIX)
98+
if prevcont is None:
99+
parentblock[key] = self._curblock
100+
elif isinstance(prevcont, list) and len(prevcont) > 0 and isinstance(prevcont[0], dict):
101+
prevcont.append(self._curblock)
102+
elif isinstance(prevcont, dict):
103+
parentblock[key] = [prevcont, self._curblock]
104+
else:
105+
parentblock[key] = [{None: prevcont}, self._curblock]
106+
107+
if attrib and prevcont is None:
108+
parentblock[key + ATTRIB_SUFFIX] = attrib
109+
elif prevcont is not None:
110+
prevattrib = parentblock.get(key + ATTRIB_SUFFIX)
111+
if isinstance(prevattrib, list):
112+
prevattrib.append(attrib)
113+
else:
114+
parentblock[key + ATTRIB_SUFFIX] = [prevattrib, attrib]
115+
116+
if self._include_hsd_attribs:
117+
if self._lower_tag_names:
118+
hsdattrib = {} if hsdattrib is None else hsdattrib
119+
hsdattrib[HSD_ATTRIB_NAME] = tagname
120+
if prevcont is None:
121+
parentblock[key + HSD_ATTRIB_SUFFIX] = hsdattrib
122+
else:
123+
prevhsdattrib = parentblock.get(key + HSD_ATTRIB_SUFFIX)
106124
if isinstance(prevhsdattrib, list):
107125
prevhsdattrib.append(hsdattrib)
108126
else:
109-
parentblock[tagname + HSD_ATTRIB_SUFFIX] = [prevhsdattrib,
110-
hsdattrib]
127+
parentblock[key + HSD_ATTRIB_SUFFIX] = [prevhsdattrib, hsdattrib]
111128
self._curblock = parentblock
112129
self._data = None
113130

@@ -189,8 +206,12 @@ def walk(self, dictobj):
189206
elif isinstance(value, list) and value and isinstance(value[0], dict):
190207
for ind, item in enumerate(value):
191208
hsdattr = hsdattrib[ind] if hsdattrib else None
192-
self._eventhandler.open_tag(key, None, hsdattr)
193-
self.walk(item)
209+
attr = attrib[ind] if attrib else None
210+
self._eventhandler.open_tag(key, attr, hsdattr)
211+
if None in item:
212+
self._eventhandler.add_text(_to_text(item[None]))
213+
else:
214+
self.walk(item)
194215
self._eventhandler.close_tag(key)
195216

196217
else:

src/hsd/eventhandler.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
#------------------------------------------------------------------------------#
2-
# hsd-python: package for manipulating HSD-formatted data in Python #
3-
# Copyright (C) 2011 - 2021 DFTB+ developers group #
4-
# Licensed under the BSD 2-clause license. #
5-
#------------------------------------------------------------------------------#
1+
#--------------------------------------------------------------------------------------------------#
2+
# hsd-python: package for manipulating HSD-formatted data in Python #
3+
# Copyright (C) 2011 - 2021 DFTB+ developers group
4+
# # BSD 2-clause license.
5+
#
6+
#--------------------------------------------------------------------------------------------------#
67
#
78
"""
89
Contains an event handler base class.

0 commit comments

Comments
 (0)