summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Doxyfile25
-rw-r--r--examples/read_conf.py2
-rw-r--r--pom_parser/__init__.py73
4 files changed, 93 insertions, 8 deletions
diff --git a/.gitignore b/.gitignore
index 77cc008..57400c8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
dist
venv
*.pyc
+doc
diff --git a/Doxyfile b/Doxyfile
new file mode 100644
index 0000000..6babbdb
--- /dev/null
+++ b/Doxyfile
@@ -0,0 +1,25 @@
+DOXYFILE_ENCODING = UTF-8
+PROJECT_NAME = pom_parser
+PROJECT_BRIEF = "Parser for the POM configuration language"
+OUTPUT_DIRECTORY = doc
+CREATE_SUBDIRS = NO
+OUTPUT_LANGUAGE = English
+MARKDOWN_SUPPORT = YES
+INPUT_ENCODING = UTF-8
+INPUT = pom_parser/__init__.py
+RECURSIVE = NO
+GENERATE_HTML = YES
+HTML_OUTPUT = .
+HTML_FILE_EXTENSION = .html
+GENERATE_LATEX = NO
+ENABLE_PREPROCESSING = YES
+MACRO_EXPANSION = YES
+MULTILINE_CPP_IS_BRIEF = YES
+AUTOLINK_SUPPORT = NO
+DISTRIBUTE_GROUP_DOC = YES
+EXTRACT_PRIVATE = NO
+COLLABORATION_GRAPH = NO
+WARN_IF_UNDOCUMENTED = YES
+QUIET = YES
+INCLUDE_GRAPH = NO
+WARN_AS_ERROR = FAIL_ON_WARNINGS
diff --git a/examples/read_conf.py b/examples/read_conf.py
index 936157e..bbee83b 100644
--- a/examples/read_conf.py
+++ b/examples/read_conf.py
@@ -9,6 +9,6 @@ import pom_parser
try:
filename = 'examples/conf.pom' if len(sys.argv) < 2 else sys.argv[1]
conf = pom_parser.load_path(filename)
- print(conf.get_list('file-extensions.C',['a']))
+ print(conf.location('file-extensions'))
except pom_parser.Error as e:
print('Parse error:', str(e), sep = '\n')
diff --git a/pom_parser/__init__.py b/pom_parser/__init__.py
index c179158..e904d95 100644
--- a/pom_parser/__init__.py
+++ b/pom_parser/__init__.py
@@ -2,6 +2,22 @@ import io
from typing import Optional, Any, Iterable, Iterator
class Error(ValueError):
+ r'''!An error raised by pom_parser.
+
+Attributes
+----------
+- `next: Optional[Error]` -
+ Next error (used when there are multiple errors in a file)
+- `message: str` -
+ Error message as a string. Note that this does not include
+ file/line information, or all errors in a list, so you most
+ likely want to use str(error) instead.
+- `file: str` -
+ File name where error occurred.
+- `line: int` -
+ Line number where error occurred.
+'''
+
next: Optional['Error']
message: str
file: str
@@ -27,6 +43,22 @@ class Error(ValueError):
return l[0]
class Item:
+ r'''!
+An item (key-value pair) in a POM configuration.
+
+Attributes
+----------
+- `key: str` -
+ The key.
+- `value: str` -
+ The value.
+- `file: str` -
+ 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
@@ -113,20 +145,34 @@ class Item:
return list_
class Configuration:
+ '''!A POM configuration.'''
_items: dict[str, Item]
+ _section_locations: dict[str, tuple[str, int]]
def __repr__(self) -> str:
result = []
for item in self._items.values():
result.append(f'{item.key}: {repr(item.value)}')
return '\n'.join(result)
+ def _init(self, items: dict[str, Item]) -> None:
+ self._items = items
+ self._section_locations = {}
+ for item in self._items.values():
+ for i in range(len(item.key)):
+ if item.key[i] != '.':
+ continue
+ section = item.key[:i]
+ if section not in self._section_locations \
+ or self._section_locations[section][1] > item.line:
+ self._section_locations[section] = (item.file, item.line)
+
def has(self, key: str) -> bool:
return key in self._items
def location(self, key: str) -> Optional[tuple[str, int]]:
item = self._items.get(key)
if item is None:
- return item
+ return self._section_locations.get(key, None)
return (item.file, item.line)
def get(self, key: str, default: Optional[str] = None) -> Optional[str]:
@@ -143,7 +189,8 @@ class Configuration:
item.read = True
uint = item._parse_uint()
if uint is None:
- raise item._error(f'Value {repr(item.value)} for {item.key} is not a valid (non-negative) integer.')
+ raise item._error(f'Value {repr(item.value)} for {item.key} is '
+ 'not a valid (non-negative) integer.')
return uint
def get_int(self, key: str, default: Optional[int] = None) -> Optional[int]:
@@ -173,7 +220,8 @@ class Configuration:
item.read = True
boolv = item._parse_bool()
if boolv is None:
- raise item._error(f'Value {repr(item.value)} for {item.key} is invalid (want on/off/yes/no/true/false)')
+ raise item._error(f'Value {repr(item.value)} for {item.key} is '
+ 'invalid (want on/off/yes/no/true/false)')
return boolv
def get_list(self, key: str, default: Optional[list[str]] = None) -> Optional[list[str]]:
@@ -203,9 +251,20 @@ class Configuration:
item_copy = copy.copy(item)
section_items[item.key[len(name_dot):]] = item_copy
conf = Configuration()
- conf._items = section_items
+ conf._init(section_items)
+ return conf
+
+ def merge(self, other: 'Configuration') -> 'Configuration':
+ import copy
+ new_items = {key: copy.copy(item) for key, item in other._items.items()}
+ for key, item in self._items:
+ if key not in new_items:
+ new_items[key] = copy.copy(item)
+ conf = Configuration()
+ conf._init(new_items)
return conf
+
def _parse_hex_digit(d: Optional[str]) -> Optional[int]:
if d in list('0123456789'):
return ord(d) - ord('0')
@@ -215,7 +274,7 @@ def _parse_hex_digit(d: Optional[str]) -> Optional[int]:
return ord(d) - ord('A') + 10
return None
-class _Parser:
+class __Parser:
line_number: int
filename: str
current_section: str
@@ -383,13 +442,13 @@ class _Parser:
return True
def load_file(filename: str, file: io.BufferedIOBase) -> Configuration:
- parser = _Parser(filename, file)
+ parser = __Parser(filename, file)
while parser._parse_line():
pass
if parser.errors:
raise Error._from_list(parser.errors)
conf = Configuration()
- conf._items = parser.items
+ conf._init(parser.items)
return conf
def load_string(filename: str, string: str) -> Configuration: