senfd 0.2.2__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. {senfd-0.2.2/src/senfd.egg-info → senfd-0.3.0}/PKG-INFO +11 -6
  2. {senfd-0.2.2 → senfd-0.3.0}/README.rst +2 -2
  3. {senfd-0.2.2 → senfd-0.3.0}/pyproject.toml +15 -9
  4. senfd-0.3.0/src/senfd/__init__.py +1 -0
  5. {senfd-0.2.2 → senfd-0.3.0}/src/senfd/cli.py +53 -8
  6. {senfd-0.2.2 → senfd-0.3.0}/src/senfd/documents/base.py +28 -26
  7. senfd-0.3.0/src/senfd/documents/enriched.py +1170 -0
  8. senfd-0.3.0/src/senfd/documents/merged.py +36 -0
  9. senfd-0.3.0/src/senfd/documents/model.py +98 -0
  10. {senfd-0.2.2 → senfd-0.3.0}/src/senfd/documents/plain.py +32 -18
  11. senfd-0.3.0/src/senfd/errors.py +87 -0
  12. senfd-0.3.0/src/senfd/models.py +94 -0
  13. senfd-0.3.0/src/senfd/pipeline.py +30 -0
  14. senfd-0.3.0/src/senfd/schemas/enriched.figure.document.schema.json +3238 -0
  15. {senfd-0.2.2 → senfd-0.3.0}/src/senfd/schemas/plain.figure.document.schema.json +6 -2
  16. senfd-0.3.0/src/senfd/skiplist.py +143 -0
  17. senfd-0.3.0/src/senfd/tables.py +36 -0
  18. senfd-0.3.0/src/senfd/templates/enriched.figure.document.html.jinja2 +183 -0
  19. senfd-0.2.2/src/senfd/templates/enriched.figure.document.html.jinja2 → senfd-0.3.0/src/senfd/templates/plain.figure.document.html.jinja2 +1 -4
  20. {senfd-0.2.2 → senfd-0.3.0/src/senfd.egg-info}/PKG-INFO +11 -6
  21. {senfd-0.2.2 → senfd-0.3.0}/src/senfd.egg-info/SOURCES.txt +10 -1
  22. {senfd-0.2.2 → senfd-0.3.0}/src/senfd.egg-info/requires.txt +7 -1
  23. {senfd-0.2.2 → senfd-0.3.0}/tests/test_case_conversion.py +0 -1
  24. senfd-0.3.0/tests/test_cli.py +73 -0
  25. senfd-0.3.0/tests/test_enrichments.py +104 -0
  26. senfd-0.3.0/tests/test_module.py +28 -0
  27. senfd-0.3.0/tests/test_release.py +55 -0
  28. senfd-0.3.0/tests/test_skiplist.py +41 -0
  29. senfd-0.3.0/tests/test_to_file.py +55 -0
  30. senfd-0.2.2/src/senfd/__init__.py +0 -1
  31. senfd-0.2.2/src/senfd/documents/enriched.py +0 -248
  32. senfd-0.2.2/src/senfd/errors.py +0 -28
  33. senfd-0.2.2/src/senfd/pipeline.py +0 -27
  34. senfd-0.2.2/src/senfd/schemas/enriched.figure.document.schema.json +0 -1047
  35. senfd-0.2.2/src/senfd/tables.py +0 -77
  36. senfd-0.2.2/tests/test_cli.py +0 -39
  37. senfd-0.2.2/tests/test_module.py +0 -15
  38. {senfd-0.2.2 → senfd-0.3.0}/LICENSE +0 -0
  39. {senfd-0.2.2 → senfd-0.3.0}/setup.cfg +0 -0
  40. {senfd-0.2.2 → senfd-0.3.0}/src/senfd/documents/__init__.py +0 -0
  41. {senfd-0.2.2 → senfd-0.3.0}/src/senfd/schemas/__init__.py +0 -0
  42. {senfd-0.2.2 → senfd-0.3.0}/src/senfd/templates/__init__.py +0 -0
  43. /senfd-0.2.2/src/senfd/templates/plain.figure.document.html.jinja2 → /senfd-0.3.0/src/senfd/templates/model.document.html.jinja2 +0 -0
  44. {senfd-0.2.2 → senfd-0.3.0}/src/senfd/utils.py +0 -0
  45. {senfd-0.2.2 → senfd-0.3.0}/src/senfd.egg-info/dependency_links.txt +0 -0
  46. {senfd-0.2.2 → senfd-0.3.0}/src/senfd.egg-info/entry_points.txt +0 -0
  47. {senfd-0.2.2 → senfd-0.3.0}/src/senfd.egg-info/top_level.txt +0 -0
  48. {senfd-0.2.2 → senfd-0.3.0}/tests/test_schema.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: senfd
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: Semantic Enhancer for Figure Documents
5
5
  Author-email: "Simon A. F. Lund" <os@safl.dk>
6
6
  License: LGPL-2.1-only
@@ -8,12 +8,17 @@ Description-Content-Type: text/x-rst
8
8
  License-File: LICENSE
9
9
  Requires-Dist: jinja2
10
10
  Requires-Dist: pydantic
11
- Requires-Dist: pytest
12
- Requires-Dist: pytest-cov
13
11
  Requires-Dist: python-docx
12
+ Requires-Dist: tomli
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest; extra == "dev"
15
+ Requires-Dist: pytest-cov; extra == "dev"
16
+ Provides-Extra: all
17
+ Requires-Dist: senfd[dev]; extra == "all"
18
+ Dynamic: license-file
14
19
 
15
- senfd: Semantic Enricher for NVMe Figure Documents
16
- ==================================================
20
+ senfd: Semantic Enricher for Non-mentioned-Trademark Figure Documents
21
+ =====================================================================
17
22
 
18
23
  .. image:: https://img.shields.io/badge/docs-GitHub%20Pages-blue
19
24
  :target: https://safl.github.io/senfd/
@@ -1,5 +1,5 @@
1
- senfd: Semantic Enricher for NVMe Figure Documents
2
- ==================================================
1
+ senfd: Semantic Enricher for Non-mentioned-Trademark Figure Documents
2
+ =====================================================================
3
3
 
4
4
  .. image:: https://img.shields.io/badge/docs-GitHub%20Pages-blue
5
5
  :target: https://safl.github.io/senfd/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "senfd"
3
- version = "0.2.2"
3
+ dynamic = ["version"]
4
4
  description = "Semantic Enhancer for Figure Documents"
5
5
  authors = [
6
6
  { name = "Simon A. F. Lund", email = "os@safl.dk" }
@@ -9,12 +9,18 @@ license = { text = "LGPL-2.1-only" }
9
9
  dependencies = [
10
10
  "jinja2",
11
11
  "pydantic",
12
- "pytest",
13
- "pytest-cov",
14
12
  "python-docx",
13
+ "tomli"
15
14
  ]
16
15
  readme = { file = "README.rst", content-type = "text/x-rst" }
17
16
 
17
+ [project.optional-dependencies]
18
+ dev = [
19
+ "pytest",
20
+ "pytest-cov",
21
+ ]
22
+ all = ["senfd[dev]"]
23
+
18
24
  [project.scripts]
19
25
  senfd = "senfd.cli:main"
20
26
 
@@ -22,20 +28,20 @@ senfd = "senfd.cli:main"
22
28
  requires = ["setuptools>=42", "wheel"]
23
29
  build-backend = "setuptools.build_meta"
24
30
 
31
+ [tool.setuptools.dynamic]
32
+ version = { attr = "senfd.__version__" }
33
+
25
34
  [tool.setuptools.packages.find]
26
35
  where = ["src"]
27
36
 
28
37
  [tool.setuptools.package-data]
29
38
  "*" = ["*.jinja2", "*.schema.json"]
30
39
 
31
- [tool.black]
32
- line-length = 88
33
-
34
- [tool.isort]
35
- profile = "black"
36
-
37
40
  [tool.pytest.ini_options]
38
41
  addopts = "--maxfail=1 --disable-warnings"
39
42
  testpaths = [
40
43
  "tests",
41
44
  ]
45
+
46
+ [tool.ruff]
47
+ line-length = 88
@@ -0,0 +1 @@
1
+ __version__ = "0.3.0"
@@ -6,22 +6,48 @@ Produces organized and semantically enriched ``.json`` documents from
6
6
  """
7
7
 
8
8
  import json
9
- from argparse import ArgumentParser, Namespace
9
+ from argparse import ArgumentParser, Namespace, RawTextHelpFormatter
10
10
  from pathlib import Path
11
11
  from typing import List
12
12
 
13
13
  import senfd
14
- import senfd.errors
15
14
  import senfd.pipeline
16
15
  import senfd.schemas
17
16
  from senfd.documents import get_document_classes
18
17
  from senfd.documents.base import to_file
18
+ from senfd.documents.merged import FromFolder
19
+ from senfd.errors import Error
19
20
 
21
+ PARSEARGS_EPILOG = """\
22
+ Senfd
23
+ =====
20
24
 
21
- def to_log_file(
22
- errors: List[senfd.errors.TableError], filename: str, output: Path
23
- ) -> Path:
25
+ The --skip-figures flag specifies a path to a TOML file containing a list of
26
+ figures to be skipped during processing.
24
27
 
28
+ The TOML file must follow this format:
29
+
30
+ [[skip]]
31
+ regex = "^Decimal.and.Binary.Units$"
32
+ matches = 1
33
+ description = ""
34
+
35
+ [[skip]]
36
+ regex = "^Byte, Word, and Dword Relationships$"
37
+ matches = 1
38
+ description = ""
39
+
40
+
41
+ Each entry under [[skip]] consists of:
42
+ - regex: A regular expression matching the figure description.
43
+ - matches: The expected number of occurrences of this figure in the document.
44
+ - description: An optional text field describing the reason for skipping.
45
+
46
+ This allows flexible and pattern-based skipping of figures during execution.
47
+ """
48
+
49
+
50
+ def to_log_file(errors: List[Error], filename: str, output: Path) -> Path:
25
51
  content = json.dumps(
26
52
  [{"type": type(error).__name__, **error.model_dump()} for error in errors],
27
53
  indent=4,
@@ -33,7 +59,11 @@ def to_log_file(
33
59
  def parse_args() -> Namespace:
34
60
  """Return command-line arguments"""
35
61
 
36
- parser = ArgumentParser(description="Semantically organize and enrich figures")
62
+ parser = ArgumentParser(
63
+ description="Semantically organize and enrich figures",
64
+ epilog=PARSEARGS_EPILOG,
65
+ formatter_class=RawTextHelpFormatter,
66
+ )
37
67
  parser.add_argument(
38
68
  "document", nargs="*", type=Path, help="path to one or more document(s)"
39
69
  )
@@ -48,6 +78,11 @@ def parse_args() -> Namespace:
48
78
  action="store_true",
49
79
  help="dump schema(s) and exit",
50
80
  )
81
+ parser.add_argument(
82
+ "--skip-figures",
83
+ type=Path,
84
+ help="Path to toml file that contains list of figure classifiers to skip",
85
+ )
51
86
  parser.add_argument(
52
87
  "--version",
53
88
  action="store_true",
@@ -75,8 +110,18 @@ def main() -> int:
75
110
  docclass.to_schema_file(args.output)
76
111
  return 0
77
112
 
78
- for path in args.document:
79
- errors = senfd.pipeline.process(path, args.output)
113
+ for count, path in enumerate(sorted(args.document), 1):
114
+ args.output.mkdir(parents=True, exist_ok=True)
115
+
116
+ errors = senfd.pipeline.process(path, args.output, args)
80
117
  to_log_file(errors, path.stem, args.output)
81
118
 
119
+ if FromFolder.is_applicable(args.output): # Merge ModelDocuments
120
+ merged, errors = FromFolder.convert(args.output, args)
121
+ merged.to_json_file(args.output / merged.json_filename())
122
+
82
123
  return 0
124
+
125
+
126
+ if __name__ == "__main__":
127
+ main()
@@ -2,23 +2,24 @@
2
2
  Documents
3
3
  =========
4
4
 
5
- The following classes model documents at different stages of parsing / processing an
6
- NVMe specification document from a raw extract into something semantically rich.
7
-
5
+ The following classes model documents at different stages of parsing / processing a
6
+ specification document from a raw extract into something semantically rich.
8
7
  """
9
8
 
10
9
  import importlib.resources as pkg_resources
11
10
  import json
12
11
  from abc import ABC, abstractmethod
12
+ from argparse import Namespace
13
13
  from pathlib import Path
14
14
  from typing import Any, ClassVar, Dict, List, Optional, Tuple
15
15
 
16
16
  from jinja2 import Environment, PackageLoader, select_autoescape
17
- from pydantic import BaseModel, Field, ValidationError
17
+ from pydantic import BaseModel, Field
18
18
 
19
19
  import senfd.schemas
20
20
  import senfd.tables
21
- from senfd.errors import TableError
21
+ import senfd.utils
22
+ from senfd.errors import Error
22
23
 
23
24
  TRANSLATION_TABLE: Dict[int, str] = str.maketrans(
24
25
  {
@@ -63,7 +64,7 @@ def to_file(content: str, filename: str, path: Optional[Path] = None):
63
64
  if path.is_dir():
64
65
  path = path / filename
65
66
 
66
- path.write_text(content)
67
+ path.write_text(content, encoding="utf-8")
67
68
 
68
69
  return path
69
70
 
@@ -104,7 +105,7 @@ class Document(BaseModel):
104
105
  """Writes the document JSON schema to file at the given 'path'"""
105
106
 
106
107
  return to_file(
107
- json.dumps(cls.schema(), indent=4),
108
+ json.dumps(cls.model_json_schema(), indent=4),
108
109
  cls.schema_filename(),
109
110
  path,
110
111
  )
@@ -122,7 +123,7 @@ class Document(BaseModel):
122
123
  def to_json(self) -> str:
123
124
  """Returns the document as a JSON-formatted string"""
124
125
 
125
- return self.model_dump_json(indent=4)
126
+ return self.model_dump_json()
126
127
 
127
128
  def to_json_file(self, path: Optional[Path] = None) -> Path:
128
129
  """Writes the document, formatted as JSON, to file at the given 'path'"""
@@ -132,42 +133,43 @@ class Document(BaseModel):
132
133
  def html_filename(self) -> str:
133
134
  return f"{self.meta.stem}{self.SUFFIX_HTML}"
134
135
 
135
- def to_html(self) -> str:
136
+ def to_html(self, errors: List[Error] = []) -> str:
136
137
  """Returns the document as a HTML-formatted string"""
137
138
 
138
139
  env = Environment(
139
140
  loader=PackageLoader("senfd", "templates"),
140
141
  autoescape=select_autoescape(["html", "xml"]),
141
142
  )
143
+ env.filters["snake_to_pascal"] = senfd.utils.snake_to_pascal
144
+ env.filters["pascal_to_snake"] = senfd.utils.pascal_to_snake
142
145
  template = env.get_template(self.FILENAME_HTML_TEMPLATE)
143
146
 
144
- return template.render(document=self.dict())
145
-
146
- def to_html_file(self, path: Optional[Path] = None) -> Path:
147
- """Writes the document to HTML-formatted file"""
147
+ figure_errors: Dict[int, List[Error]] = {}
148
+ for error in errors:
149
+ if not hasattr(error, "figure_nr"):
150
+ continue
148
151
 
149
- return to_file(self.to_html(), self.html_filename(), path)
152
+ if error.figure_nr not in figure_errors:
153
+ figure_errors[error.figure_nr] = []
154
+ figure_errors[error.figure_nr].append(error)
150
155
 
151
- def is_valid(self) -> bool:
152
- """Returns True when validator raises no exceptions, False otherwise"""
156
+ return template.render(document=self.model_dump(), figure_errors=figure_errors)
153
157
 
154
- try:
155
- self.validate(self.dict())
156
- except ValidationError as e:
157
- print(e)
158
- return False
158
+ def to_html_file(
159
+ self, path: Optional[Path] = None, errors: List[Error] = []
160
+ ) -> Path:
161
+ """Writes the document to HTML-formatted file"""
159
162
 
160
- return True
163
+ return to_file(self.to_html(errors), self.html_filename(), path)
161
164
 
162
165
 
163
166
  class Converter(ABC):
164
-
165
167
  @staticmethod
166
168
  @abstractmethod
167
169
  def is_applicable(path: Path) -> bool:
168
- pass
170
+ """Check whether the converter is applicable to the given 'path'"""
169
171
 
170
172
  @staticmethod
171
173
  @abstractmethod
172
- def convert(path: Path) -> Tuple[Document, List[TableError]]:
173
- pass
174
+ def convert(path: Path, args: Namespace) -> Tuple[Document, List[Error]]:
175
+ """Convert the given 'path' into a 'Document'"""