markdown-to-confluence 0.5.3__py3-none-any.whl → 0.5.5__py3-none-any.whl

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 (38) hide show
  1. {markdown_to_confluence-0.5.3.dist-info → markdown_to_confluence-0.5.5.dist-info}/METADATA +275 -208
  2. markdown_to_confluence-0.5.5.dist-info/RECORD +57 -0
  3. {markdown_to_confluence-0.5.3.dist-info → markdown_to_confluence-0.5.5.dist-info}/WHEEL +1 -1
  4. md2conf/__init__.py +1 -1
  5. md2conf/__main__.py +61 -189
  6. md2conf/api.py +35 -69
  7. md2conf/attachment.py +4 -3
  8. md2conf/clio.py +226 -0
  9. md2conf/compatibility.py +5 -0
  10. md2conf/converter.py +239 -147
  11. md2conf/csf.py +89 -9
  12. md2conf/drawio/extension.py +3 -3
  13. md2conf/drawio/render.py +2 -0
  14. md2conf/extension.py +4 -0
  15. md2conf/external.py +25 -8
  16. md2conf/frontmatter.py +18 -6
  17. md2conf/image.py +17 -14
  18. md2conf/latex.py +8 -1
  19. md2conf/markdown.py +68 -1
  20. md2conf/mermaid/render.py +1 -1
  21. md2conf/options.py +95 -24
  22. md2conf/plantuml/extension.py +7 -7
  23. md2conf/plantuml/render.py +6 -7
  24. md2conf/png.py +10 -6
  25. md2conf/processor.py +24 -3
  26. md2conf/publisher.py +193 -36
  27. md2conf/reflection.py +74 -0
  28. md2conf/scanner.py +16 -6
  29. md2conf/serializer.py +12 -1
  30. md2conf/svg.py +131 -109
  31. md2conf/toc.py +72 -0
  32. md2conf/xml.py +45 -0
  33. markdown_to_confluence-0.5.3.dist-info/RECORD +0 -55
  34. {markdown_to_confluence-0.5.3.dist-info → markdown_to_confluence-0.5.5.dist-info}/entry_points.txt +0 -0
  35. {markdown_to_confluence-0.5.3.dist-info → markdown_to_confluence-0.5.5.dist-info}/licenses/LICENSE +0 -0
  36. {markdown_to_confluence-0.5.3.dist-info → markdown_to_confluence-0.5.5.dist-info}/top_level.txt +0 -0
  37. {markdown_to_confluence-0.5.3.dist-info → markdown_to_confluence-0.5.5.dist-info}/zip-safe +0 -0
  38. /md2conf/{puppeteer-config.json → mermaid/puppeteer-config.json} +0 -0
md2conf/clio.py ADDED
@@ -0,0 +1,226 @@
1
+ """
2
+ Publish Markdown files to Confluence wiki.
3
+
4
+ Copyright 2022-2026, Levente Hunyadi
5
+
6
+ :see: https://github.com/hunyadi/md2conf
7
+ """
8
+
9
+ from argparse import ArgumentParser, Namespace
10
+ from dataclasses import MISSING, Field, dataclass, fields, is_dataclass
11
+ from types import NoneType, UnionType
12
+ from typing import Any, Literal, TypeVar, cast, get_args, get_origin
13
+
14
+ from .compatibility import LiteralString
15
+
16
+
17
+ class BaseOption:
18
+ @staticmethod
19
+ def field_name() -> str:
20
+ return "argument"
21
+
22
+
23
+ @dataclass
24
+ class BooleanOption(BaseOption):
25
+ true_text: LiteralString
26
+ false_text: LiteralString
27
+
28
+
29
+ def boolean_option(true_text: LiteralString, false_text: LiteralString) -> dict[str, Any]:
30
+ "Identifies a command-line argument as a boolean (on/off) flag."
31
+
32
+ return {BaseOption.field_name(): BooleanOption(true_text, false_text)}
33
+
34
+
35
+ @dataclass
36
+ class ValueOption(BaseOption):
37
+ text: LiteralString
38
+
39
+
40
+ def value_option(text: LiteralString) -> dict[str, Any]:
41
+ "Identifies a command-line argument as an option that assigns a value."
42
+
43
+ return {BaseOption.field_name(): ValueOption(text)}
44
+
45
+
46
+ class CompositeOption(BaseOption):
47
+ pass
48
+
49
+
50
+ def composite_option() -> dict[str, Any]:
51
+ "Identifies a command-line argument as a data-class that needs to be unnested."
52
+
53
+ return {BaseOption.field_name(): CompositeOption()}
54
+
55
+
56
+ T = TypeVar("T")
57
+
58
+
59
+ def _has_metadata(field: Field[Any]) -> bool:
60
+ return field.metadata.get(BaseOption.field_name()) is not None
61
+
62
+
63
+ def _get_metadata(field: Field[Any], tp: type[T]) -> T | None:
64
+ attrs = field.metadata.get(BaseOption.field_name())
65
+ if attrs is None:
66
+ return None
67
+ elif isinstance(attrs, tp):
68
+ return attrs
69
+ else:
70
+ raise TypeError(f"expected: {tp.__name__}; got: {type(attrs).__name__}")
71
+
72
+
73
+ class _OptionTreeVisitor:
74
+ "Adds arguments to a command-line argument parser by recursively visiting fields of a composite type."
75
+
76
+ parser: ArgumentParser
77
+ prefixes: list[str]
78
+
79
+ def __init__(self, parser: ArgumentParser) -> None:
80
+ self.parser = parser
81
+ self.prefixes = []
82
+
83
+ def _get_arg_name(self, arg_name: str) -> str:
84
+ return f"--{'-'.join([*self.prefixes, arg_name])}"
85
+
86
+ def _get_field_name(self, field_name: str) -> str:
87
+ return "_".join([*self.prefixes, field_name])
88
+
89
+ def _add_value_field(self, field: Field[Any], field_type: Any) -> None:
90
+ arg_name = field.name.replace("_", "-")
91
+ value_opt = _get_metadata(field, ValueOption)
92
+ if value_opt is None:
93
+ return
94
+ help_text = value_opt.text
95
+ if field.default is not MISSING and field.default is not None:
96
+ help_text += f" (default: {field.default!s})"
97
+ if isinstance(field_type, type):
98
+ metavar = field_type.__name__.upper()
99
+ else:
100
+ metavar = None
101
+ self.parser.add_argument(
102
+ self._get_arg_name(arg_name),
103
+ dest=self._get_field_name(field.name),
104
+ default=field.default,
105
+ type=field_type,
106
+ help=help_text,
107
+ metavar=metavar,
108
+ )
109
+
110
+ def _add_field_as_argument(self, field: Field[Any]) -> None:
111
+ arg_name = field.name.replace("_", "-")
112
+ if field.type is bool:
113
+ bool_opt = _get_metadata(field, BooleanOption)
114
+ if bool_opt is None:
115
+ return
116
+ true_text = bool_opt.true_text
117
+ if field.default is True:
118
+ true_text += " (default)"
119
+ self.parser.add_argument(
120
+ self._get_arg_name(arg_name),
121
+ dest=self._get_field_name(field.name),
122
+ action="store_true",
123
+ default=field.default,
124
+ help=true_text,
125
+ )
126
+ if arg_name.startswith("skip-"):
127
+ inverse_arg_name = "keep-" + arg_name.removeprefix("skip-")
128
+ elif arg_name.startswith("keep-"):
129
+ inverse_arg_name = "skip-" + arg_name.removeprefix("keep-")
130
+ else:
131
+ inverse_arg_name = f"no-{arg_name}"
132
+ false_text = bool_opt.false_text
133
+ if field.default is False:
134
+ false_text += " (default)"
135
+ self.parser.add_argument(
136
+ self._get_arg_name(inverse_arg_name),
137
+ dest=self._get_field_name(field.name),
138
+ action="store_false",
139
+ help=false_text,
140
+ )
141
+ return
142
+
143
+ origin = get_origin(field.type)
144
+ if origin is Literal:
145
+ value_opt = _get_metadata(field, ValueOption)
146
+ if value_opt is None:
147
+ return
148
+ value_text = value_opt.text
149
+ if field.default is not MISSING and field.default is not None:
150
+ value_text += f" (default: {field.default!s})"
151
+ self.parser.add_argument(
152
+ self._get_arg_name(arg_name),
153
+ dest=self._get_field_name(field.name),
154
+ choices=get_args(field.type),
155
+ default=field.default,
156
+ help=value_text,
157
+ )
158
+
159
+ elif origin is UnionType:
160
+ union_types = list(get_args(field.type))
161
+ if len(union_types) != 2 or NoneType not in union_types:
162
+ raise TypeError(f"expected: `T` or `T | None` as argument type; got: {field.type}")
163
+ union_types.remove(NoneType)
164
+ required_type = union_types.pop()
165
+
166
+ self._add_value_field(field, required_type)
167
+
168
+ elif isinstance(field.type, type):
169
+ if hasattr(field.type, "__dataclass_fields__"):
170
+ composite_opt = _get_metadata(field, CompositeOption)
171
+ if composite_opt is None:
172
+ return
173
+
174
+ self.prefixes.append(arg_name)
175
+ self.add_arguments(field.type)
176
+ self.prefixes.pop()
177
+
178
+ else:
179
+ self._add_value_field(field, field.type)
180
+
181
+ elif _has_metadata(field):
182
+ raise TypeError(f"expected: known argument type; got: {field.type}")
183
+
184
+ def add_arguments(self, options_type: type[Any]) -> None:
185
+ if is_dataclass(options_type):
186
+ for field in fields(options_type):
187
+ self._add_field_as_argument(field)
188
+
189
+
190
+ def add_arguments(parser: ArgumentParser, options_type: type[Any]) -> None:
191
+ """
192
+ Adds arguments to a command-line argument parser.
193
+
194
+ :param parser: A command-line argument parser.
195
+ :param options_type: A data-class type that encapsulates configuration options.
196
+ """
197
+
198
+ _OptionTreeVisitor(parser).add_arguments(options_type)
199
+
200
+
201
+ def _get_options(args: Namespace, options_type: type[T], prefixes: tuple[str, ...]) -> T:
202
+ params: dict[str, Any] = {}
203
+ if is_dataclass(options_type): # always true, condition included for type checkers
204
+ for field in fields(options_type):
205
+ field_prefixes = (*prefixes, field.name)
206
+ if isinstance(field.type, type) and is_dataclass(field.type):
207
+ params[field.name] = _get_options(args, field.type, field_prefixes)
208
+ else:
209
+ field_param = getattr(args, "_".join(field_prefixes), MISSING)
210
+ if field_param is not MISSING:
211
+ params[field.name] = field_param
212
+ return options_type(**params)
213
+
214
+
215
+ def get_options(args: Namespace, options_type: type[T]) -> T:
216
+ """
217
+ Extracts configuration options from command-line arguments acquired by an argument parser.
218
+
219
+ :param args: Arguments acquired by a command-line argument parser.
220
+ :param options_type: A data-class type that encapsulates configuration options.
221
+ :returns: Configuration options as a data-class instance.
222
+ """
223
+
224
+ if not is_dataclass(options_type):
225
+ raise TypeError(f"expected: data-class as argument target; got: {type(options_type).__name__}")
226
+ return _get_options(args, cast(type[T], options_type), ())
md2conf/compatibility.py CHANGED
@@ -8,6 +8,11 @@ Copyright 2022-2026, Levente Hunyadi
8
8
 
9
9
  import sys
10
10
 
11
+ if sys.version_info >= (3, 11):
12
+ from typing import LiteralString as LiteralString # noqa: F401
13
+ else:
14
+ from typing_extensions import LiteralString as LiteralString # noqa: F401
15
+
11
16
  if sys.version_info >= (3, 12):
12
17
  from typing import override as override # noqa: F401
13
18
  else: