mdforge 0.1.0__tar.gz → 0.2.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 (42) hide show
  1. {mdforge-0.1.0 → mdforge-0.2.0}/LICENSE +0 -0
  2. mdforge-0.2.0/PKG-INFO +606 -0
  3. mdforge-0.2.0/README.md +582 -0
  4. mdforge-0.2.0/mdforge/__init__.py +15 -0
  5. mdforge-0.2.0/mdforge/_norm.py +107 -0
  6. mdforge-0.2.0/mdforge/_utils.py +62 -0
  7. mdforge-0.2.0/mdforge/container.py +214 -0
  8. mdforge-0.2.0/mdforge/document.py +79 -0
  9. mdforge-0.2.0/mdforge/element.py +225 -0
  10. mdforge-0.2.0/mdforge/elements/__init__.py +11 -0
  11. mdforge-0.2.0/mdforge/elements/_image.py +45 -0
  12. mdforge-0.2.0/mdforge/elements/block/__init__.py +14 -0
  13. mdforge-0.2.0/mdforge/elements/block/basic.py +109 -0
  14. mdforge-0.2.0/mdforge/elements/block/lists/__init__.py +11 -0
  15. mdforge-0.2.0/mdforge/elements/block/lists/definition.py +130 -0
  16. mdforge-0.2.0/mdforge/elements/block/lists/item.py +209 -0
  17. mdforge-0.2.0/mdforge/elements/block/section.py +49 -0
  18. mdforge-0.2.0/mdforge/elements/block/table/__init__.py +11 -0
  19. mdforge-0.2.0/mdforge/elements/block/table/_context.py +427 -0
  20. mdforge-0.2.0/mdforge/elements/block/table/_flavors/flavor.py +87 -0
  21. mdforge-0.2.0/mdforge/elements/block/table/_flavors/flavors.py +31 -0
  22. mdforge-0.2.0/mdforge/elements/block/table/_flavors/frame/__init__.py +4 -0
  23. mdforge-0.2.0/mdforge/elements/block/table/_flavors/frame/frame.py +492 -0
  24. mdforge-0.2.0/mdforge/elements/block/table/_flavors/frame/variants.py +98 -0
  25. mdforge-0.2.0/mdforge/elements/block/table/_flavors/list/__init__.py +3 -0
  26. mdforge-0.2.0/mdforge/elements/block/table/_params.py +215 -0
  27. mdforge-0.2.0/mdforge/elements/block/table/_vcell.py +177 -0
  28. mdforge-0.2.0/mdforge/elements/block/table/cell.py +151 -0
  29. mdforge-0.2.0/mdforge/elements/block/table/table.py +241 -0
  30. mdforge-0.2.0/mdforge/elements/block/text.py +45 -0
  31. mdforge-0.2.0/mdforge/elements/inline/__init__.py +11 -0
  32. mdforge-0.2.0/mdforge/elements/inline/basic.py +66 -0
  33. mdforge-0.2.0/mdforge/elements/inline/text.py +152 -0
  34. mdforge-0.2.0/mdforge/exceptions.py +21 -0
  35. mdforge-0.2.0/mdforge/types.py +23 -0
  36. {mdforge-0.1.0 → mdforge-0.2.0}/pyproject.toml +41 -16
  37. mdforge-0.1.0/PKG-INFO +0 -29
  38. mdforge-0.1.0/README.md +0 -8
  39. mdforge-0.1.0/mdforge/__init__.py +0 -13
  40. mdforge-0.1.0/mdforge/_element.py +0 -12
  41. mdforge-0.1.0/mdforge/document.py +0 -57
  42. mdforge-0.1.0/mdforge/elements.py +0 -65
File without changes
mdforge-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,606 @@
1
+ Metadata-Version: 2.1
2
+ Name: mdforge
3
+ Version: 0.2.0
4
+ Summary: Pythonic Markdown generator
5
+ Home-page: https://github.com/mm21/mdforge
6
+ Author: mm21
7
+ Author-email: mm21.dev@gmail.com
8
+ 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
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ 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
+ Requires-Dist: pyrollup (>=0.1.0,<0.2.0)
21
+ Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
22
+ Description-Content-Type: text/markdown
23
+
24
+ # MDForge
25
+ Pythonic multi-flavor Markdown generator
26
+
27
+ [![Python versions](https://img.shields.io/pypi/pyversions/mdforge.svg)](https://pypi.org/project/mdforge)
28
+ [![PyPI](https://img.shields.io/pypi/v/mdforge?color=%2334D058&label=pypi%20package)](https://pypi.org/project/mdforge)
29
+ [![Tests](./badges/tests.svg?dummy=8484744)]()
30
+ [![Coverage](./badges/cov.svg?dummy=8484744)]()
31
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
32
+
33
+ - [MDForge](#mdforge)
34
+ - [Motivation](#motivation)
35
+ - [Getting started](#getting-started)
36
+ - [Working with documents](#working-with-documents)
37
+ - [Pythonic Markdown elements](#pythonic-markdown-elements)
38
+ - [Formatting](#formatting)
39
+ - [Lists](#lists)
40
+ - [Tables](#tables)
41
+ - [Images](#images)
42
+ - [Advanced features](#advanced-features)
43
+ - [HTML attributes](#html-attributes)
44
+ - [Pandoc extensions](#pandoc-extensions)
45
+
46
+ ## Motivation
47
+
48
+ MDForge provides a Pythonic interface for generating Markdown documents programmatically. It allows you to construct a document tree and render it for various Markdown flavors, making it ideal for applications such as documentation generators and report builders. It emphasizes clean flavor-agnostic elements and type safety.
49
+
50
+ While MDForge supports the rich capabilities of [Pandoc's Markdown](https://pandoc.org/MANUAL.html#pandocs-markdown), it is designed to support generating documents with multiple flavors from the same document model. Other flavors currently planned are:
51
+
52
+ - [GitHub Flavored Markdown](https://github.github.com/gfm/)
53
+ - [MyST Markdown](https://mystmd.org/guide)
54
+
55
+ ## Getting started
56
+
57
+ First, install using pip:
58
+
59
+ ```bash
60
+ pip install mdforge
61
+ ```
62
+
63
+ Create your first Markdown document:
64
+
65
+ ```python
66
+ from mdforge import Document, Heading, Paragraph, Strong
67
+ from pathlib import Path
68
+
69
+ # create a document
70
+ doc = Document()
71
+
72
+ # add elements
73
+ doc += [
74
+ Heading("My first MDForge document"),
75
+ Paragraph("Hello, ", Strong("world"), "!"),
76
+ ]
77
+
78
+ # render pandoc-flavored Markdown document to a file
79
+ doc.render_file("doc.md", flavor="pandoc")
80
+ ```
81
+
82
+ This is rendered as:
83
+
84
+ ```markdown
85
+ # My first MDForge document
86
+
87
+ Hello, **world**!
88
+
89
+ ```
90
+
91
+ ## Working with documents
92
+
93
+ The `Document` class is the fundamental interface to create a Markdown document. It implements a [Composite pattern](https://en.wikipedia.org/wiki/Composite_pattern) wherein elements are composed using the `+=` operator:
94
+
95
+ ```python
96
+ from mdforge import BulletList, Document, Section
97
+
98
+ # create a document with frontmatter
99
+ doc = Document(frontmatter={"title": "My Document", "author": "Me"})
100
+
101
+ # create sections, automatically adding headings of the appropriate level
102
+ intro = Section("Introduction")
103
+ intro += "This is an introduction paragraph."
104
+
105
+ key_points = Section("Key Points")
106
+ key_points += BulletList(["Point 1", "Point 2", "Point 3"])
107
+
108
+ # add subsections for each point
109
+ key_points += [
110
+ Section(
111
+ "Point 1",
112
+ elements=[
113
+ "Elaboration on point 1:",
114
+ BulletList(["Point 1-a", "Point 1-b"]),
115
+ ],
116
+ ),
117
+ Section("Point 2", elements=["Elaboration on point 2."]),
118
+ Section("Point 3", elements=["Elaboration on point 3."]),
119
+ ]
120
+
121
+ # add sections to document
122
+ doc += [intro, key_points]
123
+
124
+ doc.render_file("doc.md", flavor="pandoc")
125
+ ```
126
+
127
+ This is rendered as:
128
+
129
+ ```markdown
130
+ ---
131
+ title: My Document
132
+ author: Me
133
+ ---
134
+ # Introduction
135
+
136
+ This is an introduction paragraph.
137
+
138
+ # Key Points
139
+
140
+ - Point 1
141
+ - Point 2
142
+ - Point 3
143
+ <!-- end of list -->
144
+
145
+ ## Point 1
146
+
147
+ Elaboration on point 1:
148
+
149
+ - Point 1-a
150
+ - Point 1-b
151
+ <!-- end of list -->
152
+
153
+ ## Point 2
154
+
155
+ Elaboration on point 2.
156
+
157
+ ## Point 3
158
+
159
+ Elaboration on point 3.
160
+
161
+ ```
162
+
163
+ Note the following observations:
164
+
165
+ - Sections can be nested in other sections, with optional headings
166
+ - Heading levels for sections are automatically set by their placement in the hierarchy
167
+ - Strings added to a document or section via `+=` are treated as raw text
168
+ - A comment is placed at the end of each list; this disambiguates the end of one list and the start of the next, in case there are no other elements in between
169
+
170
+ ## Pythonic Markdown elements
171
+
172
+ MDForge provides a set of Markdown elements that can be composed to create rich documents. Not all common elements are implemented yet, e.g. code blocks. Nonetheless, you can insert raw text (which may be pre-formatted as Markdown) using `+=`.
173
+
174
+ We have already seen basic elements and container elements. The following examples illustrate the use of other common elements.
175
+
176
+ ### Formatting
177
+
178
+ ```python
179
+ from mdforge import (
180
+ Document,
181
+ Emph,
182
+ Paragraph,
183
+ Strikethrough,
184
+ Strong,
185
+ Underline,
186
+ )
187
+
188
+ doc = Document()
189
+
190
+ # basic formatting
191
+ doc += Strong("Bold text")
192
+ doc += Emph("Italicized text")
193
+ doc += Strikethrough("Strikethrough text")
194
+ doc += Underline("Underlined text")
195
+
196
+ # combined formatting
197
+ doc += Strong(Emph("Bold and italicized text"))
198
+
199
+ # mixed formatting in a paragraph, automatically putting spaces between elements
200
+ doc += Paragraph(
201
+ "Normal text with",
202
+ Strong("bold"),
203
+ "and",
204
+ Emph("italic"),
205
+ "segments.",
206
+ auto_space=True,
207
+ )
208
+
209
+ doc.render_file("doc.md", flavor="pandoc")
210
+ ```
211
+
212
+ This is rendered as:
213
+
214
+ ```markdown
215
+ **Bold text**
216
+
217
+ _Italicized text_
218
+
219
+ ~~Strikethrough text~~
220
+
221
+ [Underlined text]{.underline}
222
+
223
+ **_Bold and italicized text_**
224
+
225
+ Normal text with **bold** and _italic_ segments.
226
+
227
+ ```
228
+
229
+ Note the following observations:
230
+
231
+ - Here we pass `auto_space=True` in `Paragraph()`; this will automatically insert spaces between the inline elements passed to it
232
+ - As underline is not supported in CommonMark, pandoc implements its own [underline syntax](https://pandoc.org/MANUAL.html#underline)
233
+
234
+ ### Lists
235
+
236
+ MDForge supports bullet lists, numbered lists, and definition lists:
237
+
238
+ ```python
239
+ from mdforge import (
240
+ BulletList,
241
+ DefinitionItem,
242
+ DefinitionList,
243
+ Document,
244
+ ListItem,
245
+ NumberedList,
246
+ )
247
+
248
+ doc = Document()
249
+
250
+ # bullet list
251
+ doc += BulletList(["Item 1", "Item 2", "Item 3"])
252
+
253
+ # numbered list
254
+ doc += NumberedList(["First", "Second", "Third"])
255
+
256
+ # definition list
257
+ doc += DefinitionList(
258
+ [
259
+ DefinitionItem("Term A", "Definition A"),
260
+ DefinitionItem("Term B", ["Definition B1", "Definition B2"]),
261
+ ],
262
+ compact=True,
263
+ )
264
+
265
+ # nested lists
266
+ doc += BulletList(
267
+ [
268
+ "Item 1",
269
+ ListItem(
270
+ "Item 2",
271
+ [
272
+ "Item 2-1",
273
+ "Item 2-2",
274
+ ],
275
+ ),
276
+ ]
277
+ )
278
+
279
+ # mixed nested lists
280
+ doc += BulletList(
281
+ [
282
+ "Item 1",
283
+ ListItem(
284
+ "Item 2",
285
+ NumberedList(
286
+ [
287
+ "Item 2-1",
288
+ "Item 2-2",
289
+ ]
290
+ ),
291
+ ),
292
+ ]
293
+ )
294
+
295
+ doc.render_file("doc.md", flavor="pandoc")
296
+ ```
297
+
298
+ This is rendered as:
299
+
300
+ ```markdown
301
+ - Item 1
302
+ - Item 2
303
+ - Item 3
304
+ <!-- end of list -->
305
+
306
+ 1. First
307
+ 1. Second
308
+ 1. Third
309
+ <!-- end of list -->
310
+
311
+ Term A
312
+ : Definition A
313
+
314
+ Term B
315
+ : Definition B1
316
+ : Definition B2
317
+
318
+ <!-- end of definition list -->
319
+
320
+ - Item 1
321
+ - Item 2
322
+ - Item 2-1
323
+ - Item 2-2
324
+ <!-- end of list -->
325
+ <!-- end of list -->
326
+
327
+ - Item 1
328
+ - Item 2
329
+ 1. Item 2-1
330
+ 1. Item 2-2
331
+ <!-- end of list -->
332
+ <!-- end of list -->
333
+
334
+ ```
335
+
336
+ Note the following observations:
337
+
338
+ - Here we pass `compact=True` in `DefinitionList()`; this will generate [compact definition lists](https://pandoc.org/MANUAL.html#extension-compact_definition_lists) for pandoc flavor
339
+ - As mentioned above, a comment disambiguates the end of one list and the start of the next
340
+
341
+ ### Tables
342
+
343
+ MDForge provides powerful table support with cell spanning, alignment, and formatting:
344
+
345
+ ```python
346
+ from mdforge import BulletList, Cell, Document, Table
347
+
348
+ doc = Document()
349
+
350
+ # simple table
351
+ doc += Table(
352
+ [
353
+ ["Cell 1-1", "Cell 1-2"],
354
+ ["Cell 2-1", "Cell 2-2"],
355
+ ]
356
+ )
357
+
358
+ # table with alignment
359
+ doc += Table(
360
+ [["Cell 1", "Cell 2", "Cell 3"]],
361
+ align=["left", "center", "right"],
362
+ )
363
+
364
+ # table with header and footer (needs block=True)
365
+ doc += Table(
366
+ [["Cell 1", "Cell 2"]],
367
+ header=["Header 1", "Header 2"],
368
+ footer=["Footer 1", "Footer 2"],
369
+ block=True,
370
+ )
371
+
372
+ # table with row and column spanning (needs block=True)
373
+ doc += Table(
374
+ [
375
+ ["Cell 1-1", "Cell 1-2", "Cell 1-3"],
376
+ ["Cell 2-1", "Cell 2-2", "Cell 2-3"],
377
+ ],
378
+ header=[
379
+ [Cell("Column 1", rspan=2), Cell("Columns 2 & 3", cspan=2)],
380
+ ["Column 2", "Column 3"],
381
+ ],
382
+ block=True,
383
+ )
384
+
385
+ # table with each cell wrapped in an explicit paragraph if it doesn't
386
+ # already contain block content (needs block=True)
387
+ doc += Table(
388
+ [
389
+ [
390
+ BlockContainer(
391
+ "This text is implicitly wrapped in a paragraph",
392
+ BulletList(["Item 1", "Item 2"]),
393
+ ),
394
+ "Cell 2",
395
+ "Cell 3",
396
+ ]
397
+ ],
398
+ align=["left", "center", "right"],
399
+ block=True,
400
+ loose=True,
401
+ )
402
+
403
+ # table with character widths
404
+ doc += Table(
405
+ [["Short text", "This is longer text that will be wrapped"]],
406
+ widths=[15, 20],
407
+ )
408
+
409
+ # table with percentage widths
410
+ doc += Table(
411
+ [["25% width", "75% width"]],
412
+ widths_pct=[25, 75],
413
+ )
414
+
415
+ doc.render_file("doc.md", flavor="pandoc")
416
+ ```
417
+
418
+ This is rendered as:
419
+
420
+ ```markdown
421
+ <!-- table start -->
422
+
423
+ ---------- ----------
424
+ Cell 1-1 Cell 1-2
425
+
426
+ Cell 2-1 Cell 2-2
427
+ ---------------------
428
+
429
+ <!-- table end -->
430
+
431
+ <!-- table start: align=['left', 'center', 'right'] -->
432
+
433
+ -------- -------- --------
434
+ Cell 1 Cell 2 Cell 3
435
+
436
+ --------------------------
437
+
438
+ <!-- table end -->
439
+
440
+ <!-- table start: block=True -->
441
+
442
+ +----------+----------+
443
+ | Header 1 | Header 2 |
444
+ +==========+==========+
445
+ | Cell 1 | Cell 2 |
446
+ +==========+==========+
447
+ | Footer 1 | Footer 2 |
448
+ +==========+==========+
449
+
450
+ <!-- table end -->
451
+
452
+ <!-- table start: block=True -->
453
+
454
+ +----------+---------------------+
455
+ | Column 1 | Columns 2 & 3 |
456
+ | +----------+----------+
457
+ | | Column 2 | Column 3 |
458
+ +==========+==========+==========+
459
+ | Cell 1-1 | Cell 1-2 | Cell 1-3 |
460
+ +----------+----------+----------+
461
+ | Cell 2-1 | Cell 2-2 | Cell 2-3 |
462
+ +----------+----------+----------+
463
+
464
+ <!-- table end -->
465
+
466
+ <!-- table start: align=['left', 'center', 'right'], block=True, loose=True -->
467
+
468
+ +:-----------------------------------------------+:-------------:+--------------:+
469
+ | This text is implicitly wrapped in a paragraph | <p>Cell 2</p> | <p>Cell 3</p> |
470
+ | | | |
471
+ | - Item 1 | | |
472
+ | - Item 2 | | |
473
+ | <!-- end of list --> | | |
474
+ +------------------------------------------------+---------------+---------------+
475
+
476
+ <!-- table end -->
477
+
478
+ <!-- table start: widths=[15, 20] -->
479
+
480
+ ----------------- ----------------------
481
+ Short text This is longer text
482
+ that will be wrapped
483
+
484
+ ----------------------------------------
485
+
486
+ <!-- table end -->
487
+
488
+ <!-- table start: widths_pct=[25, 75] -->
489
+
490
+ ----------- -----------------------------
491
+ 25% width 75% width
492
+
493
+ -----------------------------------------
494
+
495
+ <!-- table end -->
496
+
497
+ ```
498
+
499
+ Note the following observations:
500
+
501
+ - Merged cells and footers are only supported with `block=True`; this causes pandoc flavor to use a [grid table](https://pandoc.org/MANUAL.html#extension-grid_tables) instead of a [multiline table](https://pandoc.org/MANUAL.html#extension-multiline_tables)
502
+ - Pass `loose=True` to ensure all cells are wrapped in a paragraph, even if they don't get parsed as block content (requires `block=True`)
503
+ - This ensures consistently-padded cells
504
+ - Use `BlockContainer` to wrap multiple block elements in a single element
505
+
506
+ ### Images
507
+
508
+ MDForge supports both inline and block images:
509
+
510
+ ```python
511
+ from mdforge import BlockImage, Document, InlineImage, Paragraph
512
+
513
+ doc = Document()
514
+
515
+ # inline image in a paragraph
516
+ doc += Paragraph(
517
+ "Here is an inline image",
518
+ InlineImage("./image.png", alt_text="Inline image"),
519
+ "in a paragraph.",
520
+ auto_space=True,
521
+ )
522
+
523
+ # block image with caption and alignment
524
+ doc += BlockImage("./image.png", caption="Block image", align="center")
525
+
526
+ doc.render_file("doc.md", flavor="pandoc")
527
+ ```
528
+
529
+ This is rendered as:
530
+
531
+ ```markdown
532
+ Here is an inline image ![Inline image](./image.png) in a paragraph.
533
+
534
+ ![Block image](./image.png){fig-align="center"}
535
+
536
+ ```
537
+
538
+ Note the following observations:
539
+
540
+ - Support for caption and alignment depends on flavor
541
+
542
+ ## Advanced features
543
+
544
+ ### HTML attributes
545
+
546
+ For pandoc flavor, HTML attributes can be added to various elements:
547
+
548
+ ```python
549
+ from mdforge import Attributes, Document, Heading, Ref, Span
550
+
551
+ doc = Document()
552
+
553
+ # heading with ID, classes, and custom attributes
554
+ my_heading = Heading(
555
+ "Heading with attributes",
556
+ attributes=Attributes(
557
+ html_id="my-heading",
558
+ css_classes=["class1", "class2"],
559
+ attrs={"style": "color: blue;"},
560
+ ),
561
+ )
562
+ doc += my_heading
563
+
564
+ # span with attributes
565
+ doc += Span(
566
+ "Text with attributes",
567
+ attributes=Attributes(html_id="my-span", css_classes="class1"),
568
+ )
569
+
570
+ # reference to heading by id
571
+ doc += Ref(my_heading, "See previous heading")
572
+
573
+ doc.render_file("doc.md", flavor="pandoc")
574
+ ```
575
+
576
+ This is rendered as:
577
+
578
+ ```markdown
579
+ # Heading with attributes {#my-heading .class1 .class2 style="color: blue;"}
580
+
581
+ [Text with attributes]{#my-span .class1}
582
+
583
+ [See previous heading](#my-heading)
584
+
585
+ ```
586
+
587
+ ### Pandoc extensions
588
+
589
+ As MDForge is designed with pandoc compatibility in mind, it automatically tracks required pandoc extensions:
590
+
591
+ ```python
592
+ from mdforge import Attributes, Document, Heading, Strikethrough
593
+
594
+ doc = Document()
595
+
596
+ # requires "header_attributes"
597
+ doc += Heading("Heading 1", attributes=Attributes(html_id="heading-1"))
598
+
599
+ # requires "strikeout"
600
+ doc += Strikethrough("This text is struck through")
601
+
602
+ # get required pandoc extensions
603
+ extensions = doc.get_pandoc_extensions()
604
+ assert extensions == ["header_attributes", "strikeout"]
605
+ ```
606
+