mdforge 0.2.0__tar.gz → 0.2.2__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 (42) hide show
  1. {mdforge-0.2.0 → mdforge-0.2.2}/PKG-INFO +24 -11
  2. {mdforge-0.2.0 → mdforge-0.2.2}/README.md +14 -0
  3. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/container.py +5 -2
  4. mdforge-0.2.2/mdforge/document.py +59 -0
  5. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/element.py +29 -7
  6. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/elements/block/basic.py +1 -1
  7. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/elements/block/lists/definition.py +2 -2
  8. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/elements/block/lists/item.py +2 -2
  9. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/elements/block/section.py +1 -1
  10. mdforge-0.2.2/mdforge/elements/block/table/_context.py +35 -0
  11. mdforge-0.2.2/mdforge/elements/block/table/_flavors/flavors.py +64 -0
  12. mdforge-0.2.2/mdforge/elements/block/table/_flavors/frame/context.py +816 -0
  13. mdforge-0.2.2/mdforge/elements/block/table/_flavors/frame/variant.py +140 -0
  14. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/elements/block/table/_flavors/frame/variants.py +1 -1
  15. mdforge-0.2.2/mdforge/elements/block/table/_variant.py +56 -0
  16. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/elements/block/table/_vcell.py +3 -3
  17. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/elements/block/table/cell.py +2 -2
  18. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/elements/block/table/table.py +6 -11
  19. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/elements/inline/basic.py +1 -1
  20. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/elements/inline/text.py +1 -1
  21. {mdforge-0.2.0 → mdforge-0.2.2}/pyproject.toml +36 -31
  22. mdforge-0.2.0/mdforge/document.py +0 -79
  23. mdforge-0.2.0/mdforge/elements/block/table/_context.py +0 -427
  24. mdforge-0.2.0/mdforge/elements/block/table/_flavors/flavor.py +0 -87
  25. mdforge-0.2.0/mdforge/elements/block/table/_flavors/flavors.py +0 -31
  26. mdforge-0.2.0/mdforge/elements/block/table/_flavors/frame/frame.py +0 -492
  27. {mdforge-0.2.0 → mdforge-0.2.2}/LICENSE +0 -0
  28. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/__init__.py +0 -0
  29. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/_norm.py +0 -0
  30. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/_utils.py +0 -0
  31. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/elements/__init__.py +0 -0
  32. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/elements/_image.py +0 -0
  33. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/elements/block/__init__.py +0 -0
  34. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/elements/block/lists/__init__.py +0 -0
  35. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/elements/block/table/__init__.py +0 -0
  36. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/elements/block/table/_flavors/frame/__init__.py +0 -0
  37. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/elements/block/table/_flavors/list/__init__.py +0 -0
  38. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/elements/block/table/_params.py +0 -0
  39. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/elements/block/text.py +0 -0
  40. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/elements/inline/__init__.py +0 -0
  41. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/exceptions.py +0 -0
  42. {mdforge-0.2.0 → mdforge-0.2.2}/mdforge/types.py +0 -0
@@ -1,24 +1,23 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: mdforge
3
- Version: 0.2.0
4
- Summary: Pythonic Markdown generator
5
- Home-page: https://github.com/mm21/mdforge
3
+ Version: 0.2.2
4
+ Summary: Pythonic multi-flavor Markdown generator
6
5
  Author: mm21
7
6
  Author-email: mm21.dev@gmail.com
8
7
  Requires-Python: >=3.12,<4.0
9
- Classifier: Development Status :: 2 - Pre-Alpha
10
- Classifier: License :: OSI Approved :: MIT License
11
- Classifier: Natural Language :: English
8
+ Classifier: Programming Language :: Python :: Implementation :: CPython
12
9
  Classifier: Operating System :: OS Independent
10
+ Classifier: Typing :: Typed
11
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Natural Language :: English
14
+ Classifier: License :: OSI Approved :: MIT License
13
15
  Classifier: Programming Language :: Python :: 3
14
16
  Classifier: Programming Language :: Python :: 3.12
15
- Classifier: Programming Language :: Python :: 3.11
16
17
  Classifier: Programming Language :: Python :: 3.13
17
- Classifier: Programming Language :: Python :: Implementation :: CPython
18
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
- Classifier: Typing :: Typed
20
18
  Requires-Dist: pyrollup (>=0.1.0,<0.2.0)
21
19
  Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
20
+ Project-URL: Homepage, https://github.com/mm21/mdforge
22
21
  Description-Content-Type: text/markdown
23
22
 
24
23
  # MDForge
@@ -39,6 +38,7 @@ Pythonic multi-flavor Markdown generator
39
38
  - [Lists](#lists)
40
39
  - [Tables](#tables)
41
40
  - [Images](#images)
41
+ - [Rendering standalone elements](#rendering-standalone-elements)
42
42
  - [Advanced features](#advanced-features)
43
43
  - [HTML attributes](#html-attributes)
44
44
  - [Pandoc extensions](#pandoc-extensions)
@@ -131,6 +131,7 @@ This is rendered as:
131
131
  title: My Document
132
132
  author: Me
133
133
  ---
134
+
134
135
  # Introduction
135
136
 
136
137
  This is an introduction paragraph.
@@ -539,6 +540,18 @@ Note the following observations:
539
540
 
540
541
  - Support for caption and alignment depends on flavor
541
542
 
543
+ ### Rendering standalone elements
544
+
545
+ Any element class can be rendered by itself, even if not placed in a document. This is useful for generating document snippets which may be embedded in a document, e.g. via a templating engine.
546
+
547
+ ```python
548
+ from mdforge import InlineContainer, Strong
549
+
550
+ element = InlineContainer("This is a ", Strong("test element"))
551
+
552
+ assert element.render(flavor="pandoc") == "This is a **test element**"
553
+ ```
554
+
542
555
  ## Advanced features
543
556
 
544
557
  ### HTML attributes
@@ -16,6 +16,7 @@ Pythonic multi-flavor Markdown generator
16
16
  - [Lists](#lists)
17
17
  - [Tables](#tables)
18
18
  - [Images](#images)
19
+ - [Rendering standalone elements](#rendering-standalone-elements)
19
20
  - [Advanced features](#advanced-features)
20
21
  - [HTML attributes](#html-attributes)
21
22
  - [Pandoc extensions](#pandoc-extensions)
@@ -108,6 +109,7 @@ This is rendered as:
108
109
  title: My Document
109
110
  author: Me
110
111
  ---
112
+
111
113
  # Introduction
112
114
 
113
115
  This is an introduction paragraph.
@@ -516,6 +518,18 @@ Note the following observations:
516
518
 
517
519
  - Support for caption and alignment depends on flavor
518
520
 
521
+ ### Rendering standalone elements
522
+
523
+ Any element class can be rendered by itself, even if not placed in a document. This is useful for generating document snippets which may be embedded in a document, e.g. via a templating engine.
524
+
525
+ ```python
526
+ from mdforge import InlineContainer, Strong
527
+
528
+ element = InlineContainer("This is a ", Strong("test element"))
529
+
530
+ assert element.render(flavor="pandoc") == "This is a **test element**"
531
+ ```
532
+
519
533
  ## Advanced features
520
534
 
521
535
  ### HTML attributes
@@ -31,7 +31,7 @@ class InlineContainerMixin(BaseElement):
31
31
  __auto_space: bool
32
32
 
33
33
  def __init__(
34
- self, *elements: str | BaseInlineElement, auto_space: bool = False
34
+ self, *elements: BaseInlineElement | str, auto_space: bool = False
35
35
  ):
36
36
  def coerce(obj: str) -> BaseInlineElement:
37
37
  return coerce_inline_text(
@@ -95,7 +95,10 @@ class BaseBlockContainer(BaseBlockElement):
95
95
 
96
96
  def _render_block(self, flavor: FlavorType) -> Generator[str, None, None]:
97
97
  yield "\n\n".join(
98
- [element._render_block_lines(flavor) for element in self.__elements]
98
+ [
99
+ "\n".join(element._render_block(flavor))
100
+ for element in self.__elements
101
+ ]
99
102
  )
100
103
 
101
104
  def _get_pandoc_extensions(self) -> set[str]:
@@ -0,0 +1,59 @@
1
+ """
2
+ Interface for Markdown document generation.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Any, Generator, Iterable
8
+
9
+ import yaml
10
+
11
+ from .container import BaseLevelBlockContainer
12
+ from .element import BaseElement
13
+ from .types import FlavorType
14
+
15
+ __all__ = [
16
+ "Document",
17
+ ]
18
+
19
+ ROOT_LEVEL: int = 1
20
+
21
+
22
+ class Document(BaseLevelBlockContainer):
23
+ """
24
+ Encapsulates a Markdown document. Add elements using the `+=` operator.
25
+ """
26
+
27
+ _level_inc: int = 0
28
+ """
29
+ Treat all nested containers as top-level sections.
30
+ """
31
+
32
+ __frontmatter: dict[str, Any] | None
33
+
34
+ def __init__(
35
+ self,
36
+ *,
37
+ frontmatter: dict[str, Any] | None = None,
38
+ elements: BaseElement | str | Iterable[BaseElement | str] | None = None,
39
+ ):
40
+ super().__init__()
41
+ self.__frontmatter = frontmatter
42
+ self._level = ROOT_LEVEL
43
+
44
+ if elements:
45
+ self += elements
46
+
47
+ def _render_block(self, flavor: FlavorType) -> Generator[str, None, None]:
48
+ yield from self.__render_frontmatter()
49
+ yield from super()._render_block(flavor)
50
+ yield ""
51
+
52
+ def __render_frontmatter(self) -> Generator[str, None, None]:
53
+ if self.__frontmatter is None:
54
+ return None
55
+
56
+ content = yaml.dump(
57
+ self.__frontmatter, default_flow_style=False, sort_keys=False
58
+ )
59
+ yield from ["---", f"{content}---", ""]
@@ -6,6 +6,7 @@ from __future__ import annotations
6
6
 
7
7
  from abc import ABC, abstractmethod
8
8
  from dataclasses import dataclass
9
+ from pathlib import Path
9
10
  from typing import TYPE_CHECKING, Generator
10
11
 
11
12
  from ._norm import norm_list
@@ -23,13 +24,40 @@ __all__ = [
23
24
 
24
25
 
25
26
  class BaseElement(ABC):
27
+ """
28
+ Base renderable markdown element, which may be a container of other
29
+ elements.
30
+ """
26
31
 
27
32
  __container: BaseLevelBlockContainer | None = None
28
33
  """
29
34
  Container to which this element belongs. Must be added to a container in
30
- order to be rendered in output.
35
+ order to be rendered in a document, but otherwise can still be rendered
36
+ standalone.
31
37
  """
32
38
 
39
+ def render(self, *, flavor: FlavorType) -> str:
40
+ """
41
+ Return element as a string using the provided flavor.
42
+ """
43
+ return "\n".join(self._render_element(flavor))
44
+
45
+ def render_file(self, path: Path | str, *, flavor: FlavorType):
46
+ """
47
+ Write Markdown document using the provided flavor to the provided file.
48
+ """
49
+ path_norm = path if isinstance(path, Path) else Path(path)
50
+ with path_norm.open("w") as fh:
51
+ fh.write(self.render(flavor=flavor))
52
+
53
+ def get_pandoc_extensions(self) -> list[str]:
54
+ """
55
+ Get a list of extensions required to convert the resulting
56
+ markdown document in pandoc, assuming pandoc flavor is used for
57
+ rendering.
58
+ """
59
+ return sorted(self._get_pandoc_extensions())
60
+
33
61
  @abstractmethod
34
62
  def _render_element(self, flavor: FlavorType) -> Generator[str, None, None]:
35
63
  """
@@ -98,12 +126,6 @@ class BaseBlockElement(BaseElement):
98
126
  def _render_element(self, flavor: FlavorType) -> Generator[str, None, None]:
99
127
  yield from self._render_block(flavor)
100
128
 
101
- def _render_block_lines(self, flavor: FlavorType) -> str:
102
- """
103
- Render as multi-line string.
104
- """
105
- return "\n".join(self._render_block(flavor))
106
-
107
129
 
108
130
  @dataclass(kw_only=True)
109
131
  class Attributes:
@@ -74,7 +74,7 @@ class BlockImage(BaseBlockElement, ImageMixin):
74
74
  def __init__(
75
75
  self,
76
76
  path: str,
77
- caption: str | BaseInlineElement | None = None,
77
+ caption: BaseInlineElement | str | None = None,
78
78
  align: AlignType | None = None,
79
79
  *,
80
80
  attributes: Attributes | None = None,
@@ -31,8 +31,8 @@ class DefinitionItem:
31
31
 
32
32
  def __init__(
33
33
  self,
34
- term: str | BaseInlineElement,
35
- definitions: str | BaseElement | list[str | BaseElement],
34
+ term: BaseInlineElement | str,
35
+ definitions: BaseElement | str | list[BaseElement | str],
36
36
  ):
37
37
  """
38
38
  :param term: Term as a string or inline element
@@ -22,7 +22,7 @@ __all__ = [
22
22
  ]
23
23
 
24
24
 
25
- type ListItemType = str | BaseElement | ListItem
25
+ type ListItemType = BaseElement | ListItem | str
26
26
 
27
27
 
28
28
  class ListItem:
@@ -32,7 +32,7 @@ class ListItem:
32
32
 
33
33
  def __init__(
34
34
  self,
35
- content: str | BaseElement,
35
+ content: BaseElement | str,
36
36
  sub_items: list[ListItemType] | BaseItemList | None = None,
37
37
  ):
38
38
  self.__element = norm_obj(
@@ -28,7 +28,7 @@ class Section(BaseLevelBlockContainer):
28
28
 
29
29
  def __init__(
30
30
  self,
31
- heading: str | Heading | None = None,
31
+ heading: Heading | str | None = None,
32
32
  *,
33
33
  elements: BaseElement | str | Iterable[BaseElement | str] | None = None,
34
34
  ):
@@ -0,0 +1,35 @@
1
+ """
2
+ Encapsulates context for rendering tables.
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import Generator
7
+
8
+ from ....types import FlavorType
9
+ from ._params import TableParams
10
+ from ._variant import BaseTableVariant
11
+
12
+
13
+ class BaseRenderContext[VariantT: BaseTableVariant](ABC):
14
+ """
15
+ Encapsulates all state needed to render a table: immutable params and
16
+ variant info.
17
+ """
18
+
19
+ flavor: FlavorType
20
+ params: TableParams
21
+ variant: VariantT
22
+
23
+ def __init__(
24
+ self, flavor: FlavorType, params: TableParams, variant: VariantT
25
+ ):
26
+ self.flavor = flavor
27
+ self.params = params
28
+ self.variant = variant
29
+
30
+ @abstractmethod
31
+ def render(self) -> Generator[str, None, None]:
32
+ """
33
+ Render table.
34
+ """
35
+ ...
@@ -0,0 +1,64 @@
1
+ """
2
+ Listing of table flavors.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from dataclasses import dataclass
8
+
9
+ from .....types import FlavorType
10
+ from .._context import BaseRenderContext
11
+ from .._params import TableParams
12
+ from .._variant import VariantConfig, VariantT
13
+ from .frame.context import FrameRenderContext
14
+ from .frame.variants import GRID_VARIANT, MULTILINE_VARIANT
15
+
16
+
17
+ @dataclass(frozen=True, kw_only=True)
18
+ class TableFlavorSpec:
19
+ """
20
+ Specification of markdown table flavor.
21
+ """
22
+
23
+ variant_config: VariantConfig[VariantT]
24
+ render_context_cls: type[BaseRenderContext[VariantT]]
25
+
26
+
27
+ FLAVOR_MAP: dict[FlavorType, TableFlavorSpec] = {
28
+ "pandoc": TableFlavorSpec(
29
+ variant_config=VariantConfig(
30
+ inline=MULTILINE_VARIANT, block=GRID_VARIANT
31
+ ),
32
+ render_context_cls=FrameRenderContext,
33
+ )
34
+ }
35
+ """
36
+ Mapping of markdown flavor to table config.
37
+ """
38
+
39
+
40
+ def create_render_context(
41
+ flavor: FlavorType, params: TableParams, block: bool
42
+ ) -> BaseRenderContext:
43
+ """
44
+ Create a render context for the given markdown flavor and capabilities.
45
+ """
46
+
47
+ err = (
48
+ f"Tables for flavor {flavor} with block={block} not currently supported"
49
+ )
50
+
51
+ flavor_spec = FLAVOR_MAP.get(flavor)
52
+ assert flavor_spec, err
53
+
54
+ variant = (
55
+ flavor_spec.variant_config.block
56
+ if block
57
+ else flavor_spec.variant_config.inline
58
+ )
59
+ assert variant, err
60
+
61
+ variant.validate_params(params)
62
+
63
+ render_context = flavor_spec.render_context_cls(flavor, params, variant)
64
+ return render_context