arguebuf 2.7.2__tar.gz → 3.0.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 (137) hide show
  1. {arguebuf-2.7.2/src/arguebuf.egg-info → arguebuf-3.0.0}/PKG-INFO +25 -19
  2. {arguebuf-2.7.2 → arguebuf-3.0.0}/README.md +2 -1
  3. arguebuf-3.0.0/pyproject.toml +96 -0
  4. arguebuf-3.0.0/pyproject.toml.orig +77 -0
  5. {arguebuf-2.7.2 → arguebuf-3.0.0}/src/arguebuf/__init__.py +18 -21
  6. {arguebuf-2.7.2 → arguebuf-3.0.0}/src/arguebuf/cli/app.py +4 -1
  7. arguebuf-3.0.0/src/arguebuf/cli/graph.py +240 -0
  8. arguebuf-3.0.0/src/arguebuf/cli/model.py +148 -0
  9. arguebuf-3.0.0/src/arguebuf/cli/server.py +74 -0
  10. arguebuf-3.0.0/src/arguebuf/cli/text.py +35 -0
  11. arguebuf-3.0.0/src/arguebuf/cli/translator.py +72 -0
  12. arguebuf-3.0.0/src/arguebuf/dt.py +29 -0
  13. {arguebuf-2.7.2 → arguebuf-3.0.0}/src/arguebuf/dump/__init__.py +9 -11
  14. arguebuf-3.0.0/src/arguebuf/dump/_config.py +26 -0
  15. arguebuf-3.0.0/src/arguebuf/dump/_dump_aif.py +49 -0
  16. arguebuf-3.0.0/src/arguebuf/dump/_dump_d2.py +65 -0
  17. arguebuf-3.0.0/src/arguebuf/dump/_dump_dict.py +21 -0
  18. arguebuf-3.0.0/src/arguebuf/dump/_dump_graphviz.py +92 -0
  19. arguebuf-3.0.0/src/arguebuf/dump/_dump_io.py +13 -0
  20. {arguebuf-2.7.2 → arguebuf-3.0.0}/src/arguebuf/dump/_dump_json.py +4 -4
  21. arguebuf-3.0.0/src/arguebuf/dump/_dump_networkx.py +76 -0
  22. {arguebuf-2.7.2 → arguebuf-3.0.0}/src/arguebuf/dump/_dump_path.py +8 -4
  23. arguebuf-3.0.0/src/arguebuf/dump/_dump_protobuf.py +173 -0
  24. arguebuf-3.0.0/src/arguebuf/dump/_dump_xaif.py +25 -0
  25. arguebuf-3.0.0/src/arguebuf/load/_config.py +111 -0
  26. arguebuf-3.0.0/src/arguebuf/load/_load_aif.py +173 -0
  27. arguebuf-3.0.0/src/arguebuf/load/_load_aml.py +111 -0
  28. arguebuf-3.0.0/src/arguebuf/load/_load_argdown.py +52 -0
  29. arguebuf-3.0.0/src/arguebuf/load/_load_brat.py +52 -0
  30. arguebuf-3.0.0/src/arguebuf/load/_load_casebase.py +177 -0
  31. arguebuf-3.0.0/src/arguebuf/load/_load_dict.py +34 -0
  32. arguebuf-3.0.0/src/arguebuf/load/_load_io.py +32 -0
  33. arguebuf-3.0.0/src/arguebuf/load/_load_json.py +18 -0
  34. {arguebuf-2.7.2 → arguebuf-3.0.0}/src/arguebuf/load/_load_kialo.py +30 -49
  35. arguebuf-3.0.0/src/arguebuf/load/_load_microtexts.py +120 -0
  36. arguebuf-3.0.0/src/arguebuf/load/_load_ova.py +138 -0
  37. arguebuf-3.0.0/src/arguebuf/load/_load_path.py +76 -0
  38. arguebuf-3.0.0/src/arguebuf/load/_load_protobuf.py +182 -0
  39. arguebuf-3.0.0/src/arguebuf/load/_load_sadface.py +82 -0
  40. arguebuf-3.0.0/src/arguebuf/load/_load_text.py +26 -0
  41. arguebuf-3.0.0/src/arguebuf/load/_load_xaif.py +14 -0
  42. {arguebuf-2.7.2 → arguebuf-3.0.0}/src/arguebuf/model/__init__.py +18 -22
  43. arguebuf-3.0.0/src/arguebuf/model/analyst.py +17 -0
  44. arguebuf-3.0.0/src/arguebuf/model/edge.py +20 -0
  45. arguebuf-3.0.0/src/arguebuf/model/graph.py +515 -0
  46. arguebuf-3.0.0/src/arguebuf/model/metadata.py +14 -0
  47. arguebuf-3.0.0/src/arguebuf/model/node.py +121 -0
  48. arguebuf-3.0.0/src/arguebuf/model/participant.py +23 -0
  49. arguebuf-3.0.0/src/arguebuf/model/reference.py +20 -0
  50. arguebuf-3.0.0/src/arguebuf/model/resource.py +26 -0
  51. arguebuf-3.0.0/src/arguebuf/model/scheme.py +196 -0
  52. arguebuf-3.0.0/src/arguebuf/model/userdata.py +5 -0
  53. arguebuf-3.0.0/src/arguebuf/model/utils.py +29 -0
  54. {arguebuf-2.7.2 → arguebuf-3.0.0}/src/arguebuf/render/__init__.py +1 -1
  55. arguebuf-3.0.0/src/arguebuf/render/_render_d2.py +20 -0
  56. arguebuf-3.0.0/src/arguebuf/render/_render_graphviz.py +19 -0
  57. {arguebuf-2.7.2 → arguebuf-3.0.0}/src/arguebuf/schemas/__init__.py +3 -1
  58. arguebuf-3.0.0/src/arguebuf/schemas/aif.py +48 -0
  59. {arguebuf-2.7.2 → arguebuf-3.0.0}/src/arguebuf/schemas/argdown.py +5 -6
  60. arguebuf-3.0.0/src/arguebuf/schemas/d2.py +64 -0
  61. arguebuf-3.0.0/src/arguebuf/schemas/graphviz.py +9 -0
  62. {arguebuf-2.7.2 → arguebuf-3.0.0}/src/arguebuf/schemas/microtexts.py +2 -2
  63. {arguebuf-2.7.2 → arguebuf-3.0.0}/src/arguebuf/schemas/ova.py +2 -2
  64. {arguebuf-2.7.2 → arguebuf-3.0.0}/src/arguebuf/schemas/sadface.py +1 -2
  65. arguebuf-3.0.0/src/arguebuf/schemas/xaif.py +55 -0
  66. arguebuf-3.0.0/src/arguebuf/traverse.py +121 -0
  67. arguebuf-3.0.0/src/arguebuf/utils.py +11 -0
  68. arguebuf-2.7.2/LICENSE +0 -21
  69. arguebuf-2.7.2/PKG-INFO +0 -178
  70. arguebuf-2.7.2/pyproject.toml +0 -55
  71. arguebuf-2.7.2/setup.cfg +0 -4
  72. arguebuf-2.7.2/src/arguebuf/cli/graph.py +0 -219
  73. arguebuf-2.7.2/src/arguebuf/cli/model.py +0 -70
  74. arguebuf-2.7.2/src/arguebuf/cli/server.py +0 -60
  75. arguebuf-2.7.2/src/arguebuf/cli/text.py +0 -54
  76. arguebuf-2.7.2/src/arguebuf/cli/translator.py +0 -73
  77. arguebuf-2.7.2/src/arguebuf/dt.py +0 -19
  78. arguebuf-2.7.2/src/arguebuf/dump/_config.py +0 -19
  79. arguebuf-2.7.2/src/arguebuf/dump/_dump_aif.py +0 -56
  80. arguebuf-2.7.2/src/arguebuf/dump/_dump_d2.py +0 -106
  81. arguebuf-2.7.2/src/arguebuf/dump/_dump_dict.py +0 -23
  82. arguebuf-2.7.2/src/arguebuf/dump/_dump_graphviz.py +0 -176
  83. arguebuf-2.7.2/src/arguebuf/dump/_dump_io.py +0 -13
  84. arguebuf-2.7.2/src/arguebuf/dump/_dump_networkx.py +0 -96
  85. arguebuf-2.7.2/src/arguebuf/dump/_dump_protobuf.py +0 -187
  86. arguebuf-2.7.2/src/arguebuf/dump/_dump_xaif.py +0 -64
  87. arguebuf-2.7.2/src/arguebuf/load/_config.py +0 -31
  88. arguebuf-2.7.2/src/arguebuf/load/_load_aif.py +0 -368
  89. arguebuf-2.7.2/src/arguebuf/load/_load_aml.py +0 -238
  90. arguebuf-2.7.2/src/arguebuf/load/_load_argdown.py +0 -93
  91. arguebuf-2.7.2/src/arguebuf/load/_load_brat.py +0 -57
  92. arguebuf-2.7.2/src/arguebuf/load/_load_casebase.py +0 -165
  93. arguebuf-2.7.2/src/arguebuf/load/_load_dict.py +0 -29
  94. arguebuf-2.7.2/src/arguebuf/load/_load_io.py +0 -32
  95. arguebuf-2.7.2/src/arguebuf/load/_load_json.py +0 -18
  96. arguebuf-2.7.2/src/arguebuf/load/_load_microtexts.py +0 -126
  97. arguebuf-2.7.2/src/arguebuf/load/_load_ova.py +0 -165
  98. arguebuf-2.7.2/src/arguebuf/load/_load_path.py +0 -78
  99. arguebuf-2.7.2/src/arguebuf/load/_load_protobuf.py +0 -192
  100. arguebuf-2.7.2/src/arguebuf/load/_load_sadface.py +0 -123
  101. arguebuf-2.7.2/src/arguebuf/load/_load_text.py +0 -28
  102. arguebuf-2.7.2/src/arguebuf/load/_load_xaif.py +0 -96
  103. arguebuf-2.7.2/src/arguebuf/model/analyst.py +0 -27
  104. arguebuf-2.7.2/src/arguebuf/model/edge.py +0 -95
  105. arguebuf-2.7.2/src/arguebuf/model/graph.py +0 -697
  106. arguebuf-2.7.2/src/arguebuf/model/metadata.py +0 -21
  107. arguebuf-2.7.2/src/arguebuf/model/node.py +0 -218
  108. arguebuf-2.7.2/src/arguebuf/model/participant.py +0 -43
  109. arguebuf-2.7.2/src/arguebuf/model/reference.py +0 -31
  110. arguebuf-2.7.2/src/arguebuf/model/resource.py +0 -30
  111. arguebuf-2.7.2/src/arguebuf/model/scheme.py +0 -270
  112. arguebuf-2.7.2/src/arguebuf/model/typing.py +0 -15
  113. arguebuf-2.7.2/src/arguebuf/model/userdata.py +0 -5
  114. arguebuf-2.7.2/src/arguebuf/model/utils.py +0 -145
  115. arguebuf-2.7.2/src/arguebuf/render/_render_d2.py +0 -35
  116. arguebuf-2.7.2/src/arguebuf/render/_render_graphviz.py +0 -43
  117. arguebuf-2.7.2/src/arguebuf/schemas/aif.py +0 -34
  118. arguebuf-2.7.2/src/arguebuf/schemas/d2.py +0 -60
  119. arguebuf-2.7.2/src/arguebuf/schemas/graphviz.py +0 -12
  120. arguebuf-2.7.2/src/arguebuf/schemas/xaif.py +0 -68
  121. arguebuf-2.7.2/src/arguebuf/traverse.py +0 -126
  122. arguebuf-2.7.2/src/arguebuf/utils.py +0 -10
  123. arguebuf-2.7.2/src/arguebuf.egg-info/SOURCES.txt +0 -83
  124. arguebuf-2.7.2/src/arguebuf.egg-info/dependency_links.txt +0 -1
  125. arguebuf-2.7.2/src/arguebuf.egg-info/entry_points.txt +0 -2
  126. arguebuf-2.7.2/src/arguebuf.egg-info/requires.txt +0 -15
  127. arguebuf-2.7.2/src/arguebuf.egg-info/top_level.txt +0 -1
  128. arguebuf-2.7.2/tests/test_create_graph.py +0 -208
  129. arguebuf-2.7.2/tests/test_load_casebase.py +0 -116
  130. arguebuf-2.7.2/tests/test_load_edge.py +0 -259
  131. arguebuf-2.7.2/tests/test_load_node.py +0 -385
  132. arguebuf-2.7.2/tests/test_preprocess_dialog.py +0 -134
  133. arguebuf-2.7.2/tests/test_render_graph.py +0 -75
  134. {arguebuf-2.7.2 → arguebuf-3.0.0}/src/arguebuf/cli/__init__.py +0 -0
  135. {arguebuf-2.7.2 → arguebuf-3.0.0}/src/arguebuf/cli/__main__.py +0 -0
  136. {arguebuf-2.7.2 → arguebuf-3.0.0}/src/arguebuf/load/__init__.py +6 -6
  137. {arguebuf-2.7.2 → arguebuf-3.0.0}/src/arguebuf/py.typed +0 -0
@@ -1,28 +1,33 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arguebuf
3
- Version: 2.7.2
3
+ Version: 3.0.0
4
4
  Summary: Create and analyze argument graphs and serialize them via Protobuf
5
+ Keywords: argument-graph,argument-mining,argumentation,nlp,protobuf
6
+ Author: Mirko Lenz
5
7
  Author-email: Mirko Lenz <mirko@mirkolenz.com>
6
- Project-URL: Repository, https://github.com/recap-utr/arguebuf-python
8
+ License-Expression: MIT
9
+ Classifier: Intended Audience :: Science/Research
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Classifier: Programming Language :: Python :: 3.15
15
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
16
+ Classifier: Typing :: Typed
17
+ Requires-Dist: arg-services>=2,<3
18
+ Requires-Dist: lxml>=6,<7
19
+ Requires-Dist: networkx>=3,<4
20
+ Requires-Dist: pygraphviz>=2,<3
21
+ Requires-Dist: arg-services[grpcio]>=2,<3 ; extra == 'cli'
22
+ Requires-Dist: deepl>=1,<2 ; extra == 'cli'
23
+ Requires-Dist: typer>=0.27,<0.28 ; extra == 'cli'
24
+ Requires-Python: >=3.13, <4
25
+ Project-URL: Changelog, https://github.com/recap-utr/arguebuf-python/releases
7
26
  Project-URL: Documentation, https://arguebuf.readthedocs.io
8
27
  Project-URL: Issues, https://github.com/recap-utr/arguebuf-python/issues
9
- Project-URL: Changelog, https://github.com/recap-utr/arguebuf-python/releases
10
- Requires-Python: >=3.11
11
- Description-Content-Type: text/markdown
12
- License-File: LICENSE
13
- Requires-Dist: arg-services<2,>=1
14
- Requires-Dist: networkx<4,>=3
15
- Requires-Dist: pendulum<4,>=3
16
- Requires-Dist: graphviz<1,>=0.20
17
- Requires-Dist: lxml<6,>=5
18
- Provides-Extra: all
19
- Requires-Dist: arguebuf[cli,graphviz]; extra == "all"
28
+ Project-URL: Repository, https://github.com/recap-utr/arguebuf-python
20
29
  Provides-Extra: cli
21
- Requires-Dist: typer<1,>=0.9; extra == "cli"
22
- Requires-Dist: deepl<2,>=1; extra == "cli"
23
- Provides-Extra: graphviz
24
- Requires-Dist: pygraphviz<2,>=1; extra == "graphviz"
25
- Dynamic: license-file
30
+ Description-Content-Type: text/markdown
26
31
 
27
32
  # Arguebuf Python
28
33
 
@@ -117,8 +122,9 @@ This also makes it really easy to integrate the popular [`spacy` library](http:/
117
122
 
118
123
  ```python
119
124
  import spacy
125
+
120
126
  nlp = spacy.load("en_core_web_lg")
121
- graph = ag.load.file("graph.json", nlp=nlp)
127
+ graph = ag.load.file("graph.json", config=ag.load.Config(nlp=nlp))
122
128
  ```
123
129
 
124
130
  ### Programmatically Create a New Graph
@@ -91,8 +91,9 @@ This also makes it really easy to integrate the popular [`spacy` library](http:/
91
91
 
92
92
  ```python
93
93
  import spacy
94
+
94
95
  nlp = spacy.load("en_core_web_lg")
95
- graph = ag.load.file("graph.json", nlp=nlp)
96
+ graph = ag.load.file("graph.json", config=ag.load.Config(nlp=nlp))
96
97
  ```
97
98
 
98
99
  ### Programmatically Create a New Graph
@@ -0,0 +1,96 @@
1
+ [project]
2
+ name = "arguebuf"
3
+ version = "3.0.0"
4
+ description = "Create and analyze argument graphs and serialize them via Protobuf"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13,<4"
7
+ license = "MIT"
8
+ keywords = [
9
+ "argument-graph",
10
+ "argument-mining",
11
+ "argumentation",
12
+ "nlp",
13
+ "protobuf",
14
+ ]
15
+ classifiers = [
16
+ "Intended Audience :: Science/Research",
17
+ "Operating System :: OS Independent",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.13",
20
+ "Programming Language :: Python :: 3.14",
21
+ "Programming Language :: Python :: 3.15",
22
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
23
+ "Typing :: Typed",
24
+ ]
25
+ dependencies = [
26
+ "arg-services>=2,<3",
27
+ "lxml>=6,<7",
28
+ "networkx>=3,<4",
29
+ "pygraphviz>=2,<3",
30
+ ]
31
+
32
+ [[project.authors]]
33
+ name = "Mirko Lenz"
34
+ email = "mirko@mirkolenz.com"
35
+
36
+ [project.urls]
37
+ Changelog = "https://github.com/recap-utr/arguebuf-python/releases"
38
+ Documentation = "https://arguebuf.readthedocs.io"
39
+ Issues = "https://github.com/recap-utr/arguebuf-python/issues"
40
+ Repository = "https://github.com/recap-utr/arguebuf-python"
41
+
42
+ [project.scripts]
43
+ arguebuf = "arguebuf.cli.app:cli"
44
+
45
+ [project.optional-dependencies]
46
+ cli = [
47
+ "arg-services[grpcio]>=2,<3",
48
+ "deepl>=1,<2",
49
+ "typer>=0.27,<0.28",
50
+ ]
51
+
52
+ [dependency-groups]
53
+ dev = [
54
+ "lxml-stubs>=0.5,<0.6",
55
+ "types-networkx>=3,<4",
56
+ { include-group = "docs" },
57
+ { include-group = "test" },
58
+ ]
59
+ docs = ["pdoc>=16,<17"]
60
+ test = [
61
+ "deepdiff>=9,<10",
62
+ "pytest>=9,<10",
63
+ "pytest-cov>=7,<8",
64
+ "pytest-xdist>=3,<4",
65
+ ]
66
+
67
+ [build-system]
68
+ requires = ["uv-build>=0.13,<0.14"]
69
+ build-backend = "uv_build"
70
+
71
+ [tool.pytest]
72
+ addopts = [
73
+ "--numprocesses=auto",
74
+ "--cov=arguebuf",
75
+ "--cov-report=term-missing",
76
+ "--doctest-modules",
77
+ "--ignore=src/arguebuf/cli",
78
+ ]
79
+ doctest_optionflags = [
80
+ "ELLIPSIS",
81
+ "IGNORE_EXCEPTION_DETAIL",
82
+ "NORMALIZE_WHITESPACE",
83
+ ]
84
+ testpaths = [
85
+ "src",
86
+ "tests",
87
+ ]
88
+
89
+ [tool.ruff.lint.flake8-bugbear]
90
+ extend-immutable-calls = ["arguebuf.load._config.Config"]
91
+
92
+ [tool.ruff.lint.pydocstyle]
93
+ convention = "google"
94
+
95
+ [tool.uv.exclude-newer-package]
96
+ arg-services = false
@@ -0,0 +1,77 @@
1
+ [project]
2
+ name = "arguebuf"
3
+ version = "3.0.0"
4
+ description = "Create and analyze argument graphs and serialize them via Protobuf"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13,<4"
7
+ license = "MIT"
8
+ authors = [{ name = "Mirko Lenz", email = "mirko@mirkolenz.com" }]
9
+ keywords = ["argument-graph", "argument-mining", "argumentation", "nlp", "protobuf"]
10
+ classifiers = [
11
+ "Intended Audience :: Science/Research",
12
+ "Operating System :: OS Independent",
13
+ "Programming Language :: Python :: 3",
14
+ "Programming Language :: Python :: 3.13",
15
+ "Programming Language :: Python :: 3.14",
16
+ "Programming Language :: Python :: 3.15",
17
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
18
+ "Typing :: Typed",
19
+ ]
20
+ dependencies = [
21
+ "arg-services>=2,<3",
22
+ "lxml>=6,<7",
23
+ "networkx>=3,<4",
24
+ "pygraphviz>=2,<3",
25
+ ]
26
+
27
+ [project.urls]
28
+ Changelog = "https://github.com/recap-utr/arguebuf-python/releases"
29
+ Documentation = "https://arguebuf.readthedocs.io"
30
+ Issues = "https://github.com/recap-utr/arguebuf-python/issues"
31
+ Repository = "https://github.com/recap-utr/arguebuf-python"
32
+
33
+ [project.scripts]
34
+ arguebuf = "arguebuf.cli.app:cli"
35
+
36
+ [project.optional-dependencies]
37
+ cli = ["arg-services[grpcio]>=2,<3", "deepl>=1,<2", "typer>=0.27,<0.28"]
38
+
39
+ [dependency-groups]
40
+ dev = [
41
+ "lxml-stubs>=0.5,<0.6",
42
+ "types-networkx>=3,<4",
43
+ { include-group = "docs" },
44
+ { include-group = "test" },
45
+ ]
46
+ docs = ["pdoc>=16,<17"]
47
+ test = [
48
+ "deepdiff>=9,<10",
49
+ "pytest>=9,<10",
50
+ "pytest-cov>=7,<8",
51
+ "pytest-xdist>=3,<4",
52
+ ]
53
+
54
+ [build-system]
55
+ requires = ["uv-build>=0.13,<0.14"]
56
+ build-backend = "uv_build"
57
+
58
+ [tool.pytest]
59
+ addopts = [
60
+ "--numprocesses=auto",
61
+ "--cov=arguebuf",
62
+ "--cov-report=term-missing",
63
+ "--doctest-modules",
64
+ "--ignore=src/arguebuf/cli",
65
+ ]
66
+ doctest_optionflags = ["ELLIPSIS", "IGNORE_EXCEPTION_DETAIL", "NORMALIZE_WHITESPACE"]
67
+ testpaths = ["src", "tests"]
68
+
69
+ [tool.ruff.lint.flake8-bugbear]
70
+ # frozen and thus safe to use as default argument
71
+ extend-immutable-calls = ["arguebuf.load._config.Config"]
72
+
73
+ [tool.ruff.lint.pydocstyle]
74
+ convention = "google"
75
+
76
+ [tool.uv.exclude-newer-package]
77
+ arg-services = false
@@ -30,32 +30,29 @@ from .utils import copy
30
30
  logging.getLogger(__name__).addHandler(logging.NullHandler())
31
31
 
32
32
  __all__ = (
33
- # submodules
34
- "load",
35
- "dump",
36
- "render",
37
- "traverse",
38
- "schemas",
39
- "model",
40
- # functions
41
- "uuid",
42
- "copy",
43
- # classes
44
- "Graph",
33
+ "AbstractNode",
34
+ "Analyst",
45
35
  "AtomNode",
46
- "SchemeNode",
36
+ "AtomOrSchemeNode",
37
+ "Attack",
47
38
  "Edge",
39
+ "Graph",
48
40
  "Metadata",
49
- "Userdata",
50
- "Analyst",
51
41
  "Participant",
52
- "Resource",
42
+ "Preference",
53
43
  "Reference",
54
- "AbstractNode",
55
- "AtomOrSchemeNode",
44
+ "Rephrase",
45
+ "Resource",
56
46
  "Scheme",
47
+ "SchemeNode",
57
48
  "Support",
58
- "Attack",
59
- "Rephrase",
60
- "Preference",
49
+ "Userdata",
50
+ "copy",
51
+ "dump",
52
+ "load",
53
+ "model",
54
+ "render",
55
+ "schemas",
56
+ "traverse",
57
+ "uuid",
61
58
  )
@@ -2,7 +2,10 @@ from typer import Typer
2
2
 
3
3
  from . import graph, server, text
4
4
 
5
- cli = Typer()
5
+ cli = Typer(
6
+ help="Convert, render, translate, and serve argument graphs.",
7
+ no_args_is_help=True,
8
+ )
6
9
  cli.add_typer(
7
10
  graph.cli, name="graph", help="Commands for dealing with argument graphs."
8
11
  )
@@ -0,0 +1,240 @@
1
+ from collections.abc import Callable
2
+ from pathlib import Path
3
+ from typing import Annotated
4
+
5
+ import typer
6
+ from deepl import DeepLClient
7
+
8
+ import arguebuf as ag
9
+
10
+ from . import model
11
+ from .translator import Translator
12
+
13
+ cli = typer.Typer(no_args_is_help=True)
14
+
15
+ OutputFormat = Annotated[
16
+ ag.dump.Format, typer.Option(help="Format of the exported graphs.")
17
+ ]
18
+
19
+
20
+ @cli.command()
21
+ def translate(
22
+ input_folder: model.InputFolder,
23
+ source_lang: model.SourceLang,
24
+ target_lang: model.TargetLang,
25
+ auth_key: model.AuthKey,
26
+ input_glob: model.InputGlob,
27
+ output_folder: model.OutputFolder = None,
28
+ output_format: OutputFormat = ag.dump.Format.ARGUEBUF,
29
+ clean: model.Clean = False,
30
+ overwrite: model.Overwrite = False,
31
+ start: model.Start = 1,
32
+ ) -> None:
33
+ """Translate the texts of atom nodes, references, and resources with DeepL."""
34
+ translator = Translator(DeepLClient(auth_key), source_lang, target_lang)
35
+
36
+ for path_pair in model.pending_pairs(
37
+ input_folder, output_folder, input_glob, ".json", clean, overwrite, start
38
+ ):
39
+ graph = ag.load.file(path_pair.source)
40
+ translator.translate_graph(graph)
41
+ ag.dump.file(graph, path_pair.target, ag.dump.Config(format=output_format))
42
+
43
+
44
+ def node_label_formatter(
45
+ strip_labels: bool, strip_labels_char: str | None
46
+ ) -> Callable[[ag.AbstractNode], str] | None:
47
+ _replace_char = "–" if strip_labels_char is None else strip_labels_char
48
+
49
+ def _node_label(node: ag.AbstractNode) -> str:
50
+ return "".join(char if char.isspace() else _replace_char for char in node.label)
51
+
52
+ if strip_labels:
53
+ return _node_label
54
+
55
+ return None
56
+
57
+
58
+ @cli.command()
59
+ def render(
60
+ input_folder: model.InputFolder,
61
+ input_glob: model.InputGlob,
62
+ output_folder: model.OutputFolder = None,
63
+ output_format: Annotated[
64
+ str,
65
+ typer.Option(
66
+ help="Suffix of the rendered files that determines their format,"
67
+ " e.g., '.pdf', '.png', or '.svg'."
68
+ ),
69
+ ] = ".pdf",
70
+ strip_scheme_nodes: Annotated[
71
+ bool,
72
+ typer.Option(help="Remove scheme nodes and connect their atoms directly."),
73
+ ] = False,
74
+ strip_node_labels: Annotated[
75
+ bool,
76
+ typer.Option(help="Mask node labels, e.g., to show only the structure."),
77
+ ] = False,
78
+ strip_node_labels_char: Annotated[
79
+ str | None,
80
+ typer.Option(
81
+ help="Character replacing all non-whitespace characters of masked labels,"
82
+ " defaults to an en dash.",
83
+ show_default=False,
84
+ ),
85
+ ] = None,
86
+ edge_style: Annotated[
87
+ ag.schemas.graphviz.EdgeStyle, typer.Option(help="Shape of the edges.")
88
+ ] = ag.schemas.graphviz.EdgeStyle.STEP,
89
+ nodesep: Annotated[
90
+ float,
91
+ typer.Option(help="Minimum space between nodes of the same rank in inches."),
92
+ ] = 0.25,
93
+ ranksep: Annotated[
94
+ float, typer.Option(help="Minimum space between ranks in inches.")
95
+ ] = 0.5,
96
+ node_wrap_col: Annotated[
97
+ int, typer.Option(help="Maximum number of characters per line in labels.")
98
+ ] = 36,
99
+ node_margin: Annotated[
100
+ tuple[float, float],
101
+ typer.Option(help="Horizontal and vertical padding of labels in inches."),
102
+ ] = (0.15, 0.1),
103
+ font_name: Annotated[str, typer.Option(help="Font of the labels.")] = "Arial",
104
+ font_size: Annotated[
105
+ float, typer.Option(help="Font size of the labels in points.")
106
+ ] = 11,
107
+ clean: model.Clean = False,
108
+ overwrite: model.Overwrite = False,
109
+ start: model.Start = 1,
110
+ max_nodes: Annotated[
111
+ int, typer.Option(help="Skip graphs with more nodes than this limit.")
112
+ ] = 1000,
113
+ prog: Annotated[
114
+ str, typer.Option(help="Graphviz layout engine, e.g., 'dot' or 'neato'.")
115
+ ] = "dot",
116
+ dpi: Annotated[
117
+ int, typer.Option(help="Resolution of raster formats such as PNG.")
118
+ ] = 300,
119
+ monochrome: Annotated[
120
+ bool, typer.Option(help="Render in black and white instead of color.")
121
+ ] = False,
122
+ ) -> None:
123
+ """Render graphs as images with Graphviz."""
124
+ label_formatter = node_label_formatter(strip_node_labels, strip_node_labels_char)
125
+
126
+ for path_pair in model.pending_pairs(
127
+ input_folder, output_folder, input_glob, output_format, clean, overwrite, start
128
+ ):
129
+ g = ag.load.file(path_pair.source)
130
+
131
+ if strip_scheme_nodes:
132
+ g.strip_scheme_nodes()
133
+
134
+ gv = ag.dump.graphviz(
135
+ g,
136
+ nodesep=nodesep,
137
+ ranksep=ranksep,
138
+ wrap_col=node_wrap_col,
139
+ margin=node_margin,
140
+ font_name=font_name,
141
+ font_size=font_size,
142
+ atom_label=label_formatter,
143
+ scheme_label=label_formatter,
144
+ edge_style=edge_style,
145
+ max_nodes=max_nodes,
146
+ monochrome=monochrome,
147
+ )
148
+
149
+ # graphs exceeding `max_nodes` are skipped
150
+ if gv is not None:
151
+ ag.render.graphviz(gv, path_pair.target, prog, dpi)
152
+
153
+
154
+ @cli.command()
155
+ def convert(
156
+ input_folder: model.InputFolder,
157
+ input_glob: model.InputGlob,
158
+ output_folder: model.OutputFolder = None,
159
+ output_format: OutputFormat = ag.dump.Format.ARGUEBUF,
160
+ clean: model.Clean = False,
161
+ overwrite: model.Overwrite = False,
162
+ start: model.Start = 1,
163
+ text_folder: Annotated[
164
+ Path | None,
165
+ typer.Option(
166
+ help="Folder mirroring INPUT_FOLDER with plain texts to add as resources,"
167
+ " defaults to the text files next to the graphs.",
168
+ show_default=False,
169
+ ),
170
+ ] = None,
171
+ text_suffix: Annotated[
172
+ str, typer.Option(help="Suffix of the files in TEXT_FOLDER.")
173
+ ] = ".txt",
174
+ reconstruct_dialog: Annotated[
175
+ bool,
176
+ typer.Option(
177
+ help="Connect AIF and xAIF atoms only linked through dialog nodes"
178
+ " to the atoms of adjacent turns, otherwise they become isolated."
179
+ ),
180
+ ] = False,
181
+ ) -> None:
182
+ """Convert graphs between formats, the input format is derived from the suffix."""
183
+ load_config = ag.load.Config(reconstruct_dialog=reconstruct_dialog)
184
+ dump_config = ag.dump.Config(format=output_format)
185
+
186
+ for path_pair in model.pending_pairs(
187
+ input_folder, output_folder, input_glob, ".json", clean, overwrite, start
188
+ ):
189
+ text_file = (
190
+ text_folder
191
+ / path_pair.source.relative_to(input_folder).with_suffix(text_suffix)
192
+ if text_folder
193
+ else None
194
+ )
195
+ graph = ag.load.file(path_pair.source, text_file=text_file, config=load_config)
196
+ ag.dump.file(graph, path_pair.target, dump_config)
197
+
198
+
199
+ @cli.command()
200
+ def statistics(
201
+ input_folder: Annotated[
202
+ Path,
203
+ typer.Argument(
204
+ help="Folder to search with INPUT_GLOB.", exists=True, file_okay=False
205
+ ),
206
+ ],
207
+ input_glob: model.InputGlob,
208
+ ) -> None:
209
+ """Print node and edge counts of the selected graphs."""
210
+ files = sorted(input_folder.glob(input_glob))
211
+
212
+ graphs = [ag.load.file(file) for file in files]
213
+ atom_nodes = [len(graph.atom_nodes) for graph in graphs]
214
+ scheme_nodes = [len(graph.scheme_nodes) for graph in graphs]
215
+ edges = [len(graph.edges) for graph in graphs]
216
+
217
+ total_graphs = len(graphs)
218
+ total_atom_nodes = sum(atom_nodes)
219
+ total_scheme_nodes = sum(scheme_nodes)
220
+ total_edges = sum(edges)
221
+
222
+ typer.echo(
223
+ f"""Total Graphs: {total_graphs}
224
+
225
+ Total Atom Nodes: {total_atom_nodes}
226
+ Total Scheme Nodes: {total_scheme_nodes}
227
+ Total Edges: {total_edges}
228
+
229
+ Atom Nodes per Graph: {total_atom_nodes / total_graphs}
230
+ Scheme Nodes per Graph: {total_scheme_nodes / total_graphs}
231
+ Edges per Graph: {total_edges / total_graphs}
232
+
233
+ Max. Atom Nodes: {max(atom_nodes)}
234
+ Max. Scheme Nodes: {max(scheme_nodes)}
235
+ Max. Edges: {max(edges)}
236
+
237
+ Min. Atom Nodes: {min(atom_nodes)}
238
+ Min. Scheme Nodes: {min(scheme_nodes)}
239
+ Min. Edges: {min(edges)}"""
240
+ )
@@ -0,0 +1,148 @@
1
+ import shutil
2
+ from collections.abc import Iterator
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+ from typing import Annotated, Self
6
+
7
+ import typer
8
+
9
+ InputFolder = Annotated[
10
+ Path,
11
+ typer.Argument(
12
+ help="File or folder to process, folders are searched with INPUT_GLOB.",
13
+ exists=True,
14
+ ),
15
+ ]
16
+ InputGlob = Annotated[
17
+ str,
18
+ typer.Argument(
19
+ help="Glob pattern selecting the files within INPUT_FOLDER, e.g., '**/*.json'."
20
+ ),
21
+ ]
22
+ OutputFolder = Annotated[
23
+ Path | None,
24
+ typer.Option(
25
+ help="File or folder to write the results to, defaults to INPUT_FOLDER.",
26
+ show_default=False,
27
+ ),
28
+ ]
29
+ Clean = Annotated[
30
+ bool, typer.Option(help="Delete the output folder before processing.")
31
+ ]
32
+ Overwrite = Annotated[
33
+ bool, typer.Option(help="Overwrite existing output files instead of skipping.")
34
+ ]
35
+ Start = Annotated[
36
+ int,
37
+ typer.Option(
38
+ help="Position of the first input file to process, used to resume a run.",
39
+ min=1,
40
+ ),
41
+ ]
42
+ SourceLang = Annotated[
43
+ str, typer.Argument(help="DeepL code of the source language, e.g., 'DE'.")
44
+ ]
45
+ TargetLang = Annotated[
46
+ str, typer.Argument(help="DeepL code of the target language, e.g., 'EN-US'.")
47
+ ]
48
+ AuthKey = Annotated[
49
+ str,
50
+ typer.Option(
51
+ help="DeepL API authentication key, prefer the environment variable.",
52
+ envvar="DEEPL_AUTH_KEY",
53
+ show_default=False,
54
+ ),
55
+ ]
56
+
57
+
58
+ @dataclass(slots=True, frozen=True)
59
+ class PathPair:
60
+ """A pair of paths for modification of files."""
61
+
62
+ source: Path
63
+ target: Path
64
+
65
+ @classmethod
66
+ def create(
67
+ cls,
68
+ path_in: Path,
69
+ path_out: Path,
70
+ input_glob: str | None,
71
+ output_suffix: str | None,
72
+ ) -> list[Self]:
73
+ pairs: list[Self] = []
74
+
75
+ if path_in.is_file():
76
+ if path_out.suffix:
77
+ pairs.append(cls(path_in, path_out))
78
+ elif output_suffix:
79
+ pairs.append(cls(path_in, path_out.with_suffix(output_suffix)))
80
+ else:
81
+ raise ValueError(
82
+ "File given as input. Please also provide a file as output."
83
+ )
84
+
85
+ elif path_in.is_dir() and input_glob and output_suffix:
86
+ path_out.mkdir(parents=True, exist_ok=True)
87
+
88
+ files_in = sorted(path_in.glob(input_glob))
89
+ files_out: list[Path] = []
90
+
91
+ for file_in in files_in:
92
+ file_out = path_out / file_in.relative_to(path_in)
93
+ file_out = file_out.with_suffix(output_suffix)
94
+ file_out.parent.mkdir(parents=True, exist_ok=True)
95
+
96
+ files_out.append(file_out)
97
+
98
+ pairs.extend(
99
+ cls(file_in, file_out)
100
+ for file_in, file_out in zip(files_in, files_out, strict=True)
101
+ )
102
+
103
+ else:
104
+ raise ValueError(
105
+ "Folder given as input. Please also provide a folder as output together"
106
+ " with a shell globbing pattern and an output suffix."
107
+ )
108
+
109
+ return pairs
110
+
111
+
112
+ def _label(path_pair: PathPair | None) -> str:
113
+ return path_pair.source.name if path_pair else ""
114
+
115
+
116
+ def pending_pairs(
117
+ input_folder: Path,
118
+ output_folder: Path | None,
119
+ input_glob: str | None,
120
+ output_suffix: str | None,
121
+ clean: bool,
122
+ overwrite: bool,
123
+ start: int,
124
+ ) -> Iterator[PathPair]:
125
+ """Yield the path pairs still to be processed while showing a progress bar.
126
+
127
+ The output folder defaults to the input folder.
128
+ """
129
+ output_folder = output_folder or input_folder
130
+
131
+ if clean:
132
+ # cleaning an output folder that contains the inputs would delete them
133
+ if input_folder.resolve().is_relative_to(output_folder.resolve()):
134
+ raise typer.BadParameter(
135
+ "The output folder must not contain the input when using --clean."
136
+ )
137
+
138
+ shutil.rmtree(output_folder, ignore_errors=True)
139
+ output_folder.mkdir(parents=True)
140
+
141
+ pairs = PathPair.create(input_folder, output_folder, input_glob, output_suffix)
142
+
143
+ with typer.progressbar(
144
+ pairs[start - 1 :], item_show_func=_label, show_pos=True
145
+ ) as bar:
146
+ for path_pair in bar:
147
+ if overwrite or not path_pair.target.exists():
148
+ yield path_pair