summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/all_functions.py88
-rw-r--r--examples/read_conf.py18
-rw-r--r--pom_parser/__init__.py57
-rwxr-xr-xpre-commit.sh2
-rw-r--r--tests/location.py2
-rw-r--r--tests/parsing.py4
6 files changed, 140 insertions, 31 deletions
diff --git a/examples/all_functions.py b/examples/all_functions.py
new file mode 100644
index 0000000..b8c3786
--- /dev/null
+++ b/examples/all_functions.py
@@ -0,0 +1,88 @@
+# Put root of repository in sys.path
+# (Ordinarily you won't want to do this — this is only
+# needed to make this example work without pom_parser installed.)
+from pathlib import Path
+import sys
+sys.path.append(str(Path(__file__).parent.parent))
+
+import pom_parser
+
+filename = 'examples/conf.pom' if len(sys.argv) < 2 else sys.argv[1]
+# Ordinary usage: read configuration from file path
+conf = pom_parser.load_path(filename)
+
+with open(filename, 'rb') as f:
+ # Can also load directly from file object
+ conf = pom_parser.load_file(filename, f)
+
+# Load configuration from string
+overrides = pom_parser.load_string('<overrides>', '''tab-size = 12
+font-size = 15.5
+overrides-applied = yes''')
+
+# Print all key-value pairs in configuration
+print(str(conf))
+
+
+# Get value of key in configuration
+indentation_type = conf.get('indentation-type')
+if indentation_type is not None:
+ # Key is set
+ print('Indenting with', indentation_type)
+else:
+ # Key is not set
+ print('No indentation type specified')
+
+# Get value, or else use default
+indentation_type = conf.get('indentation-type', '<none>')
+print('Indenting with', indentation_type)
+
+# Parse value as integer
+try:
+ tab_size = conf.get_int('tab-size', 4)
+ print('Tab size:', tab_size)
+except pom_parser.Error as e:
+ # tab-size is not set to an integer
+ print('Error:', e)
+
+# get_uint doesn't allow negative values
+tab_size = conf.get_uint('tab-size', 4)
+print('Tab size:', tab_size)
+
+# Parse value as floating-point number
+font_size = conf.get_float('font-size', 12.5)
+print('font size:', font_size)
+
+# Parse value as boolean
+show_line_numbers = conf.get_bool('show-line-numbers', True)
+print('show line numbers?', 'yes' if show_line_numbers else 'no')
+
+# Parse value as list
+cpp_extensions = conf.get_list('file-extensions.Cpp', ['.cpp', '.hpp'])
+print('C++ file extensions:', cpp_extensions)
+
+# Extract section out of configuration
+file_extensions = conf.section('file-extensions')
+c_extensions = file_extensions.get_list('C', ['.c', '.h'])
+print('C file extensions:', c_extensions)
+
+plug_ins = conf.section('plug-in')
+# Iterate over unique first components of keys
+for key in plug_ins.keys():
+ # Get location where key was defined
+ location = plug_ins.location(key)
+ assert location is not None
+ _filename, line = location
+ enabled = plug_ins.get_bool(key + '.enabled', True)
+ print('Plug-in', key, 'defined at line', line, '(enabled)' if enabled else '(disabled)')
+
+# Merge configurations (this prefers values in overrides)
+overriden = conf.merge(overrides)
+
+# Iterate over key-value pairs in configuration
+for item in overriden:
+ print(item.key, ':', item.value)
+
+# Iterate over items which haven't been accessed through .get
+for key in conf.unread_keys():
+ print('Unknown key:', key)
diff --git a/examples/read_conf.py b/examples/read_conf.py
index bbee83b..4f6bf56 100644
--- a/examples/read_conf.py
+++ b/examples/read_conf.py
@@ -6,9 +6,21 @@ import sys
sys.path.append(str(Path(__file__).parent.parent))
import pom_parser
+
+filename = 'examples/conf.pom' if len(sys.argv) < 2 else sys.argv[1]
try:
- filename = 'examples/conf.pom' if len(sys.argv) < 2 else sys.argv[1]
+ # Load configuration from file
conf = pom_parser.load_path(filename)
- print(conf.location('file-extensions'))
except pom_parser.Error as e:
- print('Parse error:', str(e), sep = '\n')
+ # Handle error due to invalid configuration file
+ print('Parse error:\n' + str(e))
+ sys.exit(1)
+
+# Get value of key in configuration
+indentation_type = conf.get('indentation-type')
+if indentation_type is not None:
+ # Key is set
+ print('Indenting with', indentation_type)
+else:
+ # Key is not set
+ print('No indentation type specified')
diff --git a/pom_parser/__init__.py b/pom_parser/__init__.py
index 8c86e7b..230ecc1 100644
--- a/pom_parser/__init__.py
+++ b/pom_parser/__init__.py
@@ -1,6 +1,3 @@
-# TODO:
-# - clean up read_conf example
-# - add all_functions example
r'''Configuration for the [POM configuration file format](https://www.pom.computer).
\mainpage pom_parser
@@ -51,6 +48,7 @@ Attributes
e.next = l[i+1]
return l[0]
+
class Item:
r'''
An item (key-value pair) in a POM configuration.
@@ -65,17 +63,21 @@ Attributes
File name where item was defined.
- `line: int` -
Line number where item was defined.
-- `read: bool` -
- Has this item been accessed by a \ref pom_parser.Configuration `get_*` method?
'''
key: str
value: str
file: str
line: int
- read: bool
+ # This is a list so we can pass it around by reference
+ _read: list[bool]
+
def __repr__(self) -> str:
return f'<Item {self.key} at {self.file}:{self.line}>'
+ def read(self) -> bool:
+ '''Returns whether this item's value has been accessed through `Configuration.get_*`.'''
+ return self._read[0]
+
def _error(self, message: str) -> Error:
return Error(self.file, self.line, message)
@@ -103,6 +105,9 @@ Attributes
return None
return value
+ def _set_read(self) -> None:
+ self._read[0] = True
+
def _parse_int(self) -> Optional[int]:
sign = 1
value = self.value
@@ -162,12 +167,19 @@ class Configuration:
r'''A POM configuration.'''
_items: dict[str, Item]
_section_locations: dict[str, tuple[str, int]]
- def __repr__(self) -> str:
+ def __str__(self) -> str:
result = []
for item in self._items.values():
- result.append(f'{item.key}: {repr(item.value)}')
+ result.append(f'{item.key} = {repr(item.value)}')
return '\n'.join(result)
+ def __iter__(self) -> Iterator[Item]:
+ r'''Get all items (key-value pairs) in configuration.
+
+The order of the returned items is arbitrary and may change in future versions.'''
+ import copy
+ return iter(map(copy.copy, self._items.values()))
+
def _init(self, items: dict[str, Item]) -> None:
self._items = items
self._section_locations = {}
@@ -200,7 +212,7 @@ class Configuration:
item = self._items.get(key)
if item is None:
return default
- item.read = True
+ item._set_read()
return item.value
def get_uint(self, key: str, default: Optional[int] = None) -> Optional[int]:
@@ -215,7 +227,7 @@ not a valid unsigned integer (< 2^53).
item = self._items.get(key)
if item is None:
return None if default is None else int(default)
- item.read = True
+ item._set_read()
uint = item._parse_uint()
if uint is None:
raise item._error(f'Value {repr(item.value)} for {item.key} is '
@@ -234,7 +246,7 @@ its value is not a valid integer (with absolute value < 2^53).
item = self._items.get(key)
if item is None:
return None if default is None else int(default)
- item.read = True
+ item._set_read()
intv = item._parse_int()
if intv is None:
raise item._error(f'Value {repr(item.value)} for {item.key} is not a valid integer.')
@@ -251,7 +263,7 @@ its value is not a valid integer (with absolute value < 2^53).
item = self._items.get(key)
if item is None:
return None if default is None else float(default)
- item.read = True
+ item._set_read()
intv = item._parse_float()
if intv is None:
raise item._error(f'Value {repr(item.value)} for {item.key} is not a valid number.')
@@ -268,7 +280,7 @@ its value is not a valid integer (with absolute value < 2^53).
item = self._items.get(key)
if item is None:
return None if default is None else bool(default)
- item.read = True
+ item._set_read()
boolv = item._parse_bool()
if boolv is None:
raise item._error(f'Value {repr(item.value)} for {item.key} is '
@@ -286,17 +298,10 @@ Literal commas can be included in the list by using `\,`.
item = self._items.get(key)
if item is None:
return None if default is None else default
- item.read = True
+ item._set_read()
return item._parse_list()
- def items(self) -> Iterator[Item]:
- r'''Get all items (key-value pairs) in configuration.
-
-The order of the returned items is arbitrary and may change in future versions.'''
- import copy
- return iter(map(copy.copy, self._items.values()))
-
def keys(self) -> Iterator[str]:
r'''Get all "direct" keys (unique first components of keys) in configuration.
@@ -307,7 +312,7 @@ The order of the returned keys is arbitrary and may change in future versions.''
r'''Get all keys which have not been accessed using a `get_*` method.
The order of the returned keys is arbitrary and may change in future versions.'''
- return (item.key for item in self._items.values() if not item.read)
+ return (item.key for item in self._items.values() if not item.read())
def section(self, name: str) -> 'Configuration':
r'''Extract a "section" out of a configuration.
@@ -318,10 +323,12 @@ with `name.` (with the `name.` stripped out) and their values.
import copy
section_items = {}
name_dot = name + '.'
- for item in self.items():
+ for item in self:
if item.key.startswith(name_dot):
item_copy = copy.copy(item)
- section_items[item.key[len(name_dot):]] = item_copy
+ new_key = item.key[len(name_dot):]
+ item_copy.key = new_key
+ section_items[new_key] = item_copy
conf = Configuration()
conf._init(section_items)
return conf
@@ -513,7 +520,7 @@ class _Parser:
key = f'{self.current_section}.{relative_key}' if self.current_section else relative_key
item = Item()
item.key = key
- item.read = False
+ item._read = [False]
item.value = value
item.file = self.filename
item.line = start_line_number
diff --git a/pre-commit.sh b/pre-commit.sh
index 601a434..6b8e4bf 100755
--- a/pre-commit.sh
+++ b/pre-commit.sh
@@ -2,3 +2,5 @@
mypy . || exit 1
pylint pom_parser/__init__.py || exit 1
+which doxygen >/dev/null && { doxygen || exit 1; }
+python -m unittest tests || exit 1
diff --git a/tests/location.py b/tests/location.py
index cdac8e7..b24d70b 100644
--- a/tests/location.py
+++ b/tests/location.py
@@ -5,7 +5,7 @@ def test_path(tester: unittest.TestCase, loc_path: str) -> None:
conf_path = loc_path.replace('.locations.pom', '.pom')
locs = pom_parser.load_path(loc_path)
conf = pom_parser.load_path(conf_path)
- for item in conf.items():
+ for item in conf:
expected = locs.get_uint(item.key)
tester.assertTrue(expected is not None)
tester.assertEqual(expected, item.line, f'Incorrect line number for {item.key}')
diff --git a/tests/parsing.py b/tests/parsing.py
index 185cde9..52d3283 100644
--- a/tests/parsing.py
+++ b/tests/parsing.py
@@ -6,10 +6,10 @@ def test_path(tester: unittest.TestCase, flat_path: str) -> None:
conf = pom_parser.load_path(conf_path)
flat = pom_parser.load_path(flat_path)
conf_items = {}
- for item in conf.items():
+ for item in conf:
tester.assertTrue(flat.has(item.key), f'{conf_path} has key {item.key} but {flat_path} does not')
conf_items[item.key] = item
- for item in flat.items():
+ for item in flat:
tester.assertTrue(conf.has(item.key), f'{flat_path} has key {item.key} but {conf_path} does not')
conf_item = conf_items[item.key]
tester.assertEqual(conf_item.value, item.value, f'Values for key {item.key} do not match.')