Skip to content

Commit d1ac35b

Browse files
authored
Added a Few New Convenience Methods to Document (#180)
* Added the add_alert method to document * Fixed mistakes in docs * Rebranded alerts to alert * Added a convenience method for adding tables from csv * Cleaned up code
1 parent 0569524 commit d1ac35b

File tree

4 files changed

+118
-53
lines changed

4 files changed

+118
-53
lines changed

snakemd/document.py

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,13 @@
2323
Raw,
2424
Table,
2525
)
26-
from .templates import TableOfContents, Template
26+
from .templates import (
27+
Alert,
28+
CSVTable,
29+
Template,
30+
Checklist,
31+
TableOfContents,
32+
)
2733

2834
logger = logging.getLogger(__name__)
2935

@@ -263,15 +269,15 @@ def add_unordered_list(self, items: Iterable[str]) -> MDList:
263269
logger.info("Added unordered list to document: %r", md_list)
264270
return md_list
265271

266-
def add_checklist(self, items: Iterable[str]) -> MDList:
272+
def add_checklist(self, items: Iterable[str]) -> Checklist:
267273
"""
268274
A convenience method which adds a checklist to the document.
269275
270276
.. doctest:: document
271277
272278
>>> doc = snakemd.new_doc()
273279
>>> doc.add_checklist(["Okabe", "Mayuri", "Kurisu"])
274-
MDList(items=[...], ordered=False, checked=False)
280+
Checklist(items=[...], checked=False)
275281
>>> print(doc)
276282
- [ ] Okabe
277283
- [ ] Mayuri
@@ -282,10 +288,10 @@ def add_checklist(self, items: Iterable[str]) -> MDList:
282288
:return:
283289
the :class:`MDList` added to this Document
284290
"""
285-
md_checklist = MDList(items, checked=False)
286-
self._elements.append(md_checklist)
287-
logger.info("Added checklist to document: %r", md_checklist)
288-
return md_checklist
291+
checklist = Checklist(items, checked=False)
292+
self._elements.append(checklist)
293+
logger.info("Added checklist to document: %r", checklist)
294+
return checklist
289295

290296
def add_table(
291297
self,
@@ -330,6 +336,33 @@ def add_table(
330336
logger.info("Added table to document: %r", table)
331337
return table
332338

339+
def add_table_from_csv(self, path: os.PathLike) -> CSVTable:
340+
"""
341+
A convenience method which adds a table from a CSV file to
342+
the document:
343+
344+
.. doctest:: document
345+
346+
>>> doc = snakemd.new_doc()
347+
>>> doc.add_table_from_csv("../tests/resources/python-support.csv")
348+
Table(header=[...], body=[...], align=None, indent=0)
349+
>>> print(doc) # doctest: +NORMALIZE_WHITESPACE
350+
| Python | 3.11 | 3.10 | 3.9 | 3.8 |
351+
| ------------------- | ---- | ---- | --- | --- |
352+
| SnakeMD >= 2.0 | Yes | Yes | Yes | Yes |
353+
| SnakeMD 0.12 - 0.15 | Yes | Yes | Yes | Yes |
354+
| SnakeMD < 0.12 | | Yes | Yes | Yes |
355+
356+
:param os.PathLike path:
357+
a path to a CSV file
358+
:return:
359+
the :class:`Table` added to this Document
360+
"""
361+
table = CSVTable(path)
362+
self._elements.append(table)
363+
logger.info("Added table to document: %r", table)
364+
return table
365+
333366
def add_code(self, code: str, lang: str = "generic") -> Code:
334367
"""
335368
A convenience method which adds a code block to the document:
@@ -433,6 +466,31 @@ def add_table_of_contents(self, levels: range = range(2, 3)) -> TableOfContents:
433466
logger.info("Added table of contents to document: %r", toc)
434467
return toc
435468

469+
def add_alert(self, message: str, kind: Alert.Kind = Alert.Kind.NOTE) -> Alert:
470+
"""
471+
A convenience method which adds an alert to the document:
472+
473+
.. doctest:: document
474+
475+
>>> doc = snakemd.new_doc()
476+
>>> doc.add_alert("Please subscribe")
477+
Quote(content=[Raw(text='[!NOTE]'), Raw(text='Please subscribe')])
478+
>>> print(doc)
479+
> [!NOTE]
480+
> Please subscribe
481+
482+
:param str message:
483+
a message that you want to stand out in your document
484+
:param Kind kind:
485+
the kind of alert; see :class:`snakemd.Alerts.Kind` for details
486+
:return:
487+
the :class:`Alert` added to this Document
488+
"""
489+
alert = Alert(message, kind)
490+
self._elements.append(alert)
491+
logger.info("Added alert to document: %r", alert)
492+
return alert
493+
436494
def scramble(self) -> None:
437495
"""
438496
A silly method which mixes all of the blocks in this document in

snakemd/elements.py

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ class Inline(Element):
108108
the line break state of the inline text
109109
110110
- defaults to :code:`False`
111-
- set to :code:`True` to add a line break to the end of the element (i.e., `<br>`)
111+
- set to :code:`True` to add a line break to the
112+
end of the element (i.e., `<br>`)
112113
"""
113114

114115
def __init__(
@@ -385,7 +386,7 @@ def uncode(self) -> Inline:
385386
"""
386387
self._code = False
387388
return self
388-
389+
389390
def breakline(self) -> Inline:
390391
"""
391392
Adds a linebreak to self.
@@ -401,7 +402,7 @@ def breakline(self) -> Inline:
401402
"""
402403
self._linebreak = True
403404
return self
404-
405+
405406
def unbreakline(self) -> Inline:
406407
"""
407408
Removes linebreak from self.
@@ -481,7 +482,7 @@ def reset(self) -> Inline:
481482
self._bold = False
482483
self._strikethrough = False
483484
return self
484-
485+
485486
def _apply_styles_from(self, text: Inline) -> Inline:
486487
"""
487488
A helper method that applies text styling to self from another
@@ -862,8 +863,9 @@ def __init__(
862863
checked if checked is None or isinstance(checked, bool) else list(checked)
863864
)
864865
self._space = ""
865-
if isinstance(self._checked, list) and MDList._top_level_count(self._items) != len(
866-
self._checked
866+
if (
867+
isinstance(self._checked, list)
868+
and MDList._top_level_count(self._items) != len(self._checked)
867869
):
868870
raise ValueError(
869871
"Number of top-level elements in checklist does not "
@@ -1117,28 +1119,27 @@ def _replace_any(self, target: str, text: Inline, count: int = -1) -> Paragraph:
11171119
if not inline_text.is_text() or target not in inline_text.get_text():
11181120
content.append(inline_text)
11191121
continue
1120-
# Split the inline element into pieces and reapply styles
1121-
else:
1122-
# Redistributes styles
1123-
items = [
1124-
Inline(item)._apply_styles_from(inline_text)
1125-
for item in inline_text.get_text().split(target, count)
1126-
]
1127-
1128-
# Adds text back in with appropriate style information
1129-
for item in items:
1130-
content.append(item)
1131-
content.append(text._apply_styles_from(inline_text))
1122+
1123+
# Split the inline element into pieces
1124+
items = [
1125+
Inline(item)._apply_styles_from(inline_text)
1126+
for item in inline_text.get_text().split(target, count)
1127+
]
1128+
1129+
# Adds text back in with appropriate style information
1130+
for item in items:
1131+
content.append(item)
1132+
content.append(text._apply_styles_from(inline_text))
1133+
content.pop()
1134+
1135+
# Trim empty strings from edges
1136+
if content[-1].get_text() == "":
11321137
content.pop()
1133-
1134-
# Trim empty strings from edges
1135-
if content[-1].get_text() == "":
1136-
content.pop()
1137-
if content[0].get_text() == "":
1138-
content = content[1:]
1139-
1140-
# Remove line breaks that may have been distributed by applying styles
1141-
content[:-1] = map(lambda item: item.unbreakline(), content[:-1])
1138+
if content[0].get_text() == "":
1139+
content = content[1:]
1140+
1141+
# Remove line breaks that may have been distributed by applying styles
1142+
content[:-1] = map(lambda item: item.unbreakline(), content[:-1])
11421143

11431144
self._content = content
11441145
return self

snakemd/templates.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ def load(self, elements: list[Element]) -> None:
5555
self._elements = elements
5656

5757

58-
class Alerts(Template):
58+
class Alert(Template):
5959
"""
60-
Alerts are a wrapper of the Quote object to provide
60+
Alert is a wrapper of the Quote object to provide
6161
support for the alerts Markdown extension. While
6262
quotes can be nested in each other, alerts cannot.
6363
@@ -77,6 +77,11 @@ class Alerts(Template):
7777
"""
7878

7979
class Kind(Enum):
80+
"""
81+
Kind is an enum representing the different
82+
kinds of alerts that you might place in a
83+
document.
84+
"""
8085
NOTE = auto()
8186
TIP = auto()
8287
IMPORTANT = auto()
@@ -85,8 +90,8 @@ class Kind(Enum):
8590

8691
def __init__(
8792
self,
88-
kind: Kind,
89-
message: str | Iterable[str | Inline | Block]
93+
message: str | Iterable[str | Inline | Block],
94+
kind: Kind
9095
) -> None:
9196
super().__init__()
9297
self._kind = kind
@@ -154,8 +159,9 @@ def __init__(
154159
checked, bool) else list(checked)
155160
)
156161
self._space = ""
157-
if isinstance(self._checked, list) and MDList._top_level_count(self._items) != len(
158-
self._checked
162+
if (
163+
isinstance(self._checked, list)
164+
and MDList._top_level_count(self._items) != len(self._checked)
159165
):
160166
raise ValueError(
161167
"Number of top-level elements in checklist does not "

tests/templates/test_alerts.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
from snakemd.elements import Inline
2-
from snakemd.templates import Alerts
2+
from snakemd.templates import Alert
33

4-
def test_alerts_note():
5-
alert = Alerts(Alerts.Kind.NOTE, "Hello, World!")
4+
def test_alert_note():
5+
alert = Alert("Hello, World!", Alert.Kind.NOTE)
66
assert str(alert) == "> [!NOTE]\n> Hello, World!"
77

8-
def test_alerts_tip():
9-
alert = Alerts(Alerts.Kind.TIP, "Hello, World!")
8+
def test_alert_tip():
9+
alert = Alert("Hello, World!", Alert.Kind.TIP)
1010
assert str(alert) == "> [!TIP]\n> Hello, World!"
1111

12-
def test_alerts_important():
13-
alert = Alerts(Alerts.Kind.IMPORTANT, "Hello, World!")
12+
def test_alert_important():
13+
alert = Alert("Hello, World!", Alert.Kind.IMPORTANT)
1414
assert str(alert) == "> [!IMPORTANT]\n> Hello, World!"
1515

16-
def test_alerts_warning():
17-
alert = Alerts(Alerts.Kind.WARNING, "Hello, World!")
16+
def test_alert_warning():
17+
alert = Alert("Hello, World!", Alert.Kind.WARNING)
1818
assert str(alert) == "> [!WARNING]\n> Hello, World!"
1919

20-
def test_alerts_caution():
21-
alert = Alerts(Alerts.Kind.CAUTION, "Hello, World!")
20+
def test_alert_caution():
21+
alert = Alert("Hello, World!", Alert.Kind.CAUTION, )
2222
assert str(alert) == "> [!CAUTION]\n> Hello, World!"
2323

24-
def test_alerts_inline():
25-
alert = Alerts(Alerts.Kind.NOTE, Inline("Hello, World!", italics=True))
24+
def test_alert_inline():
25+
alert = Alert(Inline("Hello, World!", italics=True), Alert.Kind.NOTE)
2626
assert str(alert) == "> [!NOTE]\n> _Hello, World!_"

0 commit comments

Comments
 (0)