diff --git a/snakemd/document.py b/snakemd/document.py index 25bb777..41d4a4f 100644 --- a/snakemd/document.py +++ b/snakemd/document.py @@ -23,7 +23,13 @@ Raw, Table, ) -from .templates import TableOfContents, Template +from .templates import ( + Alert, + CSVTable, + Template, + Checklist, + TableOfContents, +) logger = logging.getLogger(__name__) @@ -263,7 +269,7 @@ def add_unordered_list(self, items: Iterable[str]) -> MDList: logger.info("Added unordered list to document: %r", md_list) return md_list - def add_checklist(self, items: Iterable[str]) -> MDList: + def add_checklist(self, items: Iterable[str]) -> Checklist: """ A convenience method which adds a checklist to the document. @@ -271,7 +277,7 @@ def add_checklist(self, items: Iterable[str]) -> MDList: >>> doc = snakemd.new_doc() >>> doc.add_checklist(["Okabe", "Mayuri", "Kurisu"]) - MDList(items=[...], ordered=False, checked=False) + Checklist(items=[...], checked=False) >>> print(doc) - [ ] Okabe - [ ] Mayuri @@ -282,10 +288,10 @@ def add_checklist(self, items: Iterable[str]) -> MDList: :return: the :class:`MDList` added to this Document """ - md_checklist = MDList(items, checked=False) - self._elements.append(md_checklist) - logger.info("Added checklist to document: %r", md_checklist) - return md_checklist + checklist = Checklist(items, checked=False) + self._elements.append(checklist) + logger.info("Added checklist to document: %r", checklist) + return checklist def add_table( self, @@ -330,6 +336,33 @@ def add_table( logger.info("Added table to document: %r", table) return table + def add_table_from_csv(self, path: os.PathLike) -> CSVTable: + """ + A convenience method which adds a table from a CSV file to + the document: + + .. doctest:: document + + >>> doc = snakemd.new_doc() + >>> doc.add_table_from_csv("../tests/resources/python-support.csv") + Table(header=[...], body=[...], align=None, indent=0) + >>> print(doc) # doctest: +NORMALIZE_WHITESPACE + | Python | 3.11 | 3.10 | 3.9 | 3.8 | + | ------------------- | ---- | ---- | --- | --- | + | SnakeMD >= 2.0 | Yes | Yes | Yes | Yes | + | SnakeMD 0.12 - 0.15 | Yes | Yes | Yes | Yes | + | SnakeMD < 0.12 | | Yes | Yes | Yes | + + :param os.PathLike path: + a path to a CSV file + :return: + the :class:`Table` added to this Document + """ + table = CSVTable(path) + self._elements.append(table) + logger.info("Added table to document: %r", table) + return table + def add_code(self, code: str, lang: str = "generic") -> Code: """ 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: logger.info("Added table of contents to document: %r", toc) return toc + def add_alert(self, message: str, kind: Alert.Kind = Alert.Kind.NOTE) -> Alert: + """ + A convenience method which adds an alert to the document: + + .. doctest:: document + + >>> doc = snakemd.new_doc() + >>> doc.add_alert("Please subscribe") + Quote(content=[Raw(text='[!NOTE]'), Raw(text='Please subscribe')]) + >>> print(doc) + > [!NOTE] + > Please subscribe + + :param str message: + a message that you want to stand out in your document + :param Kind kind: + the kind of alert; see :class:`snakemd.Alerts.Kind` for details + :return: + the :class:`Alert` added to this Document + """ + alert = Alert(message, kind) + self._elements.append(alert) + logger.info("Added alert to document: %r", alert) + return alert + def scramble(self) -> None: """ A silly method which mixes all of the blocks in this document in diff --git a/snakemd/elements.py b/snakemd/elements.py index c6a034c..c3c9921 100644 --- a/snakemd/elements.py +++ b/snakemd/elements.py @@ -108,7 +108,8 @@ class Inline(Element): the line break state of the inline text - defaults to :code:`False` - - set to :code:`True` to add a line break to the end of the element (i.e., `
`) + - set to :code:`True` to add a line break to the + end of the element (i.e., `
`) """ def __init__( @@ -385,7 +386,7 @@ def uncode(self) -> Inline: """ self._code = False return self - + def breakline(self) -> Inline: """ Adds a linebreak to self. @@ -401,7 +402,7 @@ def breakline(self) -> Inline: """ self._linebreak = True return self - + def unbreakline(self) -> Inline: """ Removes linebreak from self. @@ -481,7 +482,7 @@ def reset(self) -> Inline: self._bold = False self._strikethrough = False return self - + def _apply_styles_from(self, text: Inline) -> Inline: """ A helper method that applies text styling to self from another @@ -862,8 +863,9 @@ def __init__( checked if checked is None or isinstance(checked, bool) else list(checked) ) self._space = "" - if isinstance(self._checked, list) and MDList._top_level_count(self._items) != len( - self._checked + if ( + isinstance(self._checked, list) + and MDList._top_level_count(self._items) != len(self._checked) ): raise ValueError( "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: if not inline_text.is_text() or target not in inline_text.get_text(): content.append(inline_text) continue - # Split the inline element into pieces and reapply styles - else: - # Redistributes styles - items = [ - Inline(item)._apply_styles_from(inline_text) - for item in inline_text.get_text().split(target, count) - ] - - # Adds text back in with appropriate style information - for item in items: - content.append(item) - content.append(text._apply_styles_from(inline_text)) + + # Split the inline element into pieces + items = [ + Inline(item)._apply_styles_from(inline_text) + for item in inline_text.get_text().split(target, count) + ] + + # Adds text back in with appropriate style information + for item in items: + content.append(item) + content.append(text._apply_styles_from(inline_text)) + content.pop() + + # Trim empty strings from edges + if content[-1].get_text() == "": content.pop() - - # Trim empty strings from edges - if content[-1].get_text() == "": - content.pop() - if content[0].get_text() == "": - content = content[1:] - - # Remove line breaks that may have been distributed by applying styles - content[:-1] = map(lambda item: item.unbreakline(), content[:-1]) + if content[0].get_text() == "": + content = content[1:] + + # Remove line breaks that may have been distributed by applying styles + content[:-1] = map(lambda item: item.unbreakline(), content[:-1]) self._content = content return self diff --git a/snakemd/templates.py b/snakemd/templates.py index 266a66b..fe755c1 100644 --- a/snakemd/templates.py +++ b/snakemd/templates.py @@ -55,9 +55,9 @@ def load(self, elements: list[Element]) -> None: self._elements = elements -class Alerts(Template): +class Alert(Template): """ - Alerts are a wrapper of the Quote object to provide + Alert is a wrapper of the Quote object to provide support for the alerts Markdown extension. While quotes can be nested in each other, alerts cannot. @@ -77,6 +77,11 @@ class Alerts(Template): """ class Kind(Enum): + """ + Kind is an enum representing the different + kinds of alerts that you might place in a + document. + """ NOTE = auto() TIP = auto() IMPORTANT = auto() @@ -85,8 +90,8 @@ class Kind(Enum): def __init__( self, - kind: Kind, - message: str | Iterable[str | Inline | Block] + message: str | Iterable[str | Inline | Block], + kind: Kind ) -> None: super().__init__() self._kind = kind @@ -154,8 +159,9 @@ def __init__( checked, bool) else list(checked) ) self._space = "" - if isinstance(self._checked, list) and MDList._top_level_count(self._items) != len( - self._checked + if ( + isinstance(self._checked, list) + and MDList._top_level_count(self._items) != len(self._checked) ): raise ValueError( "Number of top-level elements in checklist does not " diff --git a/tests/templates/test_alerts.py b/tests/templates/test_alerts.py index 4679448..56a6b0b 100644 --- a/tests/templates/test_alerts.py +++ b/tests/templates/test_alerts.py @@ -1,26 +1,26 @@ from snakemd.elements import Inline -from snakemd.templates import Alerts +from snakemd.templates import Alert -def test_alerts_note(): - alert = Alerts(Alerts.Kind.NOTE, "Hello, World!") +def test_alert_note(): + alert = Alert("Hello, World!", Alert.Kind.NOTE) assert str(alert) == "> [!NOTE]\n> Hello, World!" -def test_alerts_tip(): - alert = Alerts(Alerts.Kind.TIP, "Hello, World!") +def test_alert_tip(): + alert = Alert("Hello, World!", Alert.Kind.TIP) assert str(alert) == "> [!TIP]\n> Hello, World!" -def test_alerts_important(): - alert = Alerts(Alerts.Kind.IMPORTANT, "Hello, World!") +def test_alert_important(): + alert = Alert("Hello, World!", Alert.Kind.IMPORTANT) assert str(alert) == "> [!IMPORTANT]\n> Hello, World!" -def test_alerts_warning(): - alert = Alerts(Alerts.Kind.WARNING, "Hello, World!") +def test_alert_warning(): + alert = Alert("Hello, World!", Alert.Kind.WARNING) assert str(alert) == "> [!WARNING]\n> Hello, World!" -def test_alerts_caution(): - alert = Alerts(Alerts.Kind.CAUTION, "Hello, World!") +def test_alert_caution(): + alert = Alert("Hello, World!", Alert.Kind.CAUTION, ) assert str(alert) == "> [!CAUTION]\n> Hello, World!" -def test_alerts_inline(): - alert = Alerts(Alerts.Kind.NOTE, Inline("Hello, World!", italics=True)) +def test_alert_inline(): + alert = Alert(Inline("Hello, World!", italics=True), Alert.Kind.NOTE) assert str(alert) == "> [!NOTE]\n> _Hello, World!_"