From 5654b6e3f76fd654fdf83ecf669d2fa3aab1041b Mon Sep 17 00:00:00 2001 From: Jeremy Grifski Date: Mon, 10 Apr 2023 13:08:57 -0400 Subject: [PATCH] String Input for Quote Now Respects Formatting (#130) * Converted quote to the use of Raw blocks * Cleaned up code comments * Updated parameter language for consistency * Added version history * Added some install instructions * Added doctests to usage docs * Converted document doctests to include object return values * Fixed up table doctest --- docs/install.rst | 14 ++++++-- docs/usage.rst | 71 ++++++++++++++++++++++++---------------- docs/version-history.rst | 8 ++++- snakemd/document.py | 48 ++++++++++++++++++--------- snakemd/elements.py | 39 +++++++++++----------- tests/test_quote.py | 5 +++ 6 files changed, 118 insertions(+), 67 deletions(-) diff --git a/docs/install.rst b/docs/install.rst index 7bc440d..da60c89 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -3,6 +3,16 @@ Installation The quick and dirty way to install SnakeMD is to use pip: -.. code-block:: +.. code-block:: shell - pip install SnakeMD + pip install snakemd + +If you'd like access to any pre-releases, you can also +install SnakeMD with the :code:`--pre` flag: + +.. code-block:: shell + + pip install --pre snakemd + +Be aware that pre-releases are not suitable for production +code. diff --git a/docs/usage.rst b/docs/usage.rst index edd4887..cadd4ff 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -7,63 +7,71 @@ SnakeMD is a Python library for building markdown documents. We can use it by importing the SnakeMD module into our program directly: -.. code-block:: python +.. testcleanup:: usage - import snakemd + import os + os.remove("README.md") + +.. doctest:: usage + + >>> import snakemd This way, we'll have access to all of the classes available in the SnakeMD module. From here, we can take advantage of a handy function to create a new document: -.. code-block:: python +.. doctest:: usage - doc = snakemd.new_doc() + >>> doc = snakemd.new_doc() This will create a new :py:class:`snakemd.Document` object. Alternatively, we can import the Document class directly: -.. code-block:: python +.. doctest:: usage - from snakemd import Document + >>> from snakemd import Document From here, we can instantiate the Document class: -.. code-block:: python +.. doctest:: usage - doc = Document() + >>> doc = Document() While there is nothing in our document currently, we can render an empty one as follows: -.. code-block:: python +.. doctest:: usage - doc.dump("README") + >>> doc.dump("README") This will create an empty README.md file in our working directory. Of course, if we want something more interesting, we'll have to add some content to our document. To start, we'll add a title to the document: -.. code-block:: python +.. doctest:: usage - doc.add_heading("Why Use SnakeMD?") + >>> doc.add_heading("Why Use SnakeMD?") + From here, we can do pretty much anything we'd like. Some quick actions might be to include a paragraph about this library as well as a list of reasons why we might use it: -.. code-block:: python +.. doctest:: usage - p = doc.add_paragraph( - """ - SnakeMD is a library for generating markdown, and here's - why you might choose to use it: - """ - ) - doc.add_unordered_list([ - "SnakeMD makes it easy to create markdown files.", - "SnakeMD has been used to automate documentation for The Renegade Coder projects." - ]) + >>> p = doc.add_paragraph( + ... """ + ... SnakeMD is a library for generating markdown, and here's + ... why you might choose to use it: + ... """ + ... ) + + >>> doc.add_unordered_list([ + ... "SnakeMD makes it easy to create markdown files.", + ... "SnakeMD has been used to automate documentation for The Renegade Coder projects." + ... ]) + One thing that's really cool about using SnakeMD is that we can build out the structure of a document before we modify it to @@ -74,21 +82,27 @@ that are generated as a result of their use. In this case, the method returns a Paragraph object which we can modify. Here's how we might insert a link to the docs: -.. code-block:: python +.. doctest:: usage - p.insert_link("SnakeMD", "https://snakemd.therenegadecoder.com") + >>> p.insert_link("SnakeMD", "https://snakemd.therenegadecoder.com") + And if all goes well, we can output the results by outputting the document like before. Or, if we just need to see the results as a string, we can convert the document to a string directly: -.. code-block:: python +.. doctest:: usage + + >>> print(doc) #doctest:+SKIP + +.. testcode:: usage + :hide: print(doc) And this is what we'll get: -.. code-block:: markdown +.. testoutput:: usage # Why Use SnakeMD? @@ -100,8 +114,7 @@ And this is what we'll get: For completion, here is a working program to generate the document from above in a file called README.md: -.. code-block:: python - :linenos: +.. testcode:: usage import snakemd diff --git a/docs/version-history.rst b/docs/version-history.rst index 81e6cfd..8d964f6 100644 --- a/docs/version-history.rst +++ b/docs/version-history.rst @@ -13,7 +13,13 @@ as follows: v2.x ---- -* v2.0.0b [:pr:`104`, :pr:`107`, :pr:`108`, :pr:`110`, :pr:`113`, :pr:`115`, :pr:`118`, :pr:`120`, :pr:`122`, :pr:`123`, :pr:`125`, :pr:`126`] +* v2.0.0b2 [:pr:`129`, :pr:`130`] + + * Converted all code snippets in docs to doctests + * Reworked string input for :code:`Quote` to pass directly through raw + * Updated language around parameters in documentation to provide a list of possible inputs and their effects + +* v2.0.0b1 [:pr:`104`, :pr:`107`, :pr:`108`, :pr:`110`, :pr:`113`, :pr:`115`, :pr:`118`, :pr:`120`, :pr:`122`, :pr:`123`, :pr:`125`, :pr:`126`] * Removed several deprecated items: diff --git a/snakemd/document.py b/snakemd/document.py index 10c3159..a2b09b5 100644 --- a/snakemd/document.py +++ b/snakemd/document.py @@ -55,7 +55,8 @@ def add_block(self, block: Block) -> Block: .. doctest:: document >>> doc = snakemd.new_doc() - >>> _ = doc.add_block(snakemd.Heading("Python is Cool!", 2)) + >>> doc.add_block(snakemd.Heading("Python is Cool!", 2)) + >>> str(doc) '## Python is Cool!' @@ -75,7 +76,8 @@ def add_raw(self, text: str) -> Raw: .. doctest:: document >>> doc = snakemd.new_doc() - >>> _ = doc.add_raw("X: 5\\nY: 4\\nZ: 3") + >>> doc.add_raw("X: 5\\nY: 4\\nZ: 3") + >>> str(doc) 'X: 5\\nY: 4\\nZ: 3' @@ -96,7 +98,8 @@ def add_heading(self, text: str, level: int = 1) -> Heading: .. doctest:: document >>> doc = snakemd.new_doc() - >>> _ = doc.add_heading("Welcome to SnakeMD!") + >>> doc.add_heading("Welcome to SnakeMD!") + >>> str(doc) '# Welcome to SnakeMD!' @@ -123,7 +126,8 @@ def add_paragraph(self, text: str) -> Paragraph: .. doctest:: document >>> doc = snakemd.new_doc() - >>> _ = doc.add_paragraph("Mitochondria is the powerhouse of the cell.") + >>> doc.add_paragraph("Mitochondria is the powerhouse of the cell.") + >>> str(doc) 'Mitochondria is the powerhouse of the cell.' @@ -144,7 +148,8 @@ def add_ordered_list(self, items: Iterable[str]) -> MDList: .. doctest:: document >>> doc = snakemd.new_doc() - >>> _ = doc.add_ordered_list(["Goku", "Piccolo", "Vegeta"]) + >>> doc.add_ordered_list(["Goku", "Piccolo", "Vegeta"]) + >>> str(doc) '1. Goku\\n2. Piccolo\\n3. Vegeta' @@ -165,7 +170,8 @@ def add_unordered_list(self, items: Iterable[str]) -> MDList: .. doctest:: document >>> doc = snakemd.new_doc() - >>> _ = doc.add_unordered_list(["Deku", "Bakugo", "Kirishima"]) + >>> doc.add_unordered_list(["Deku", "Bakugo", "Kirishima"]) + >>> str(doc) '- Deku\\n- Bakugo\\n- Kirishima' @@ -186,7 +192,8 @@ def add_checklist(self, items: Iterable[str]) -> MDList: .. doctest:: document >>> doc = snakemd.new_doc() - >>> _ = doc.add_checklist(["Okabe", "Mayuri", "Kurisu"]) + >>> doc.add_checklist(["Okabe", "Mayuri", "Kurisu"]) + >>> str(doc) '- [ ] Okabe\\n- [ ] Mayuri\\n- [ ] Kurisu' @@ -216,7 +223,8 @@ def add_table( >>> header = ["Place", "Name"] >>> rows = [["1st", "Robert"], ["2nd", "Rae"]] >>> align = [snakemd.Table.Align.CENTER, snakemd.Table.Align.RIGHT] - >>> _ = doc.add_table(header, rows, align=align) + >>> doc.add_table(header, rows, align=align) + >>> str(doc) '| Place | Name |\\n| :---: | -----: |\\n| 1st | Robert |\\n| 2nd | Rae |' @@ -246,7 +254,8 @@ def add_code(self, code: str, lang: str = "generic") -> Code: .. doctest:: document >>> doc = snakemd.new_doc() - >>> _ = doc.add_code("x = 5") + >>> doc.add_code("x = 5") + >>> str(doc) '```generic\\nx = 5\\n```' @@ -269,7 +278,8 @@ def add_quote(self, text: str) -> Quote: .. doctest:: document >>> doc = snakemd.new_doc() - >>> _ = doc.add_quote("Welcome to the Internet!") + >>> doc.add_quote("Welcome to the Internet!") + >>> str(doc) '> Welcome to the Internet!' @@ -290,7 +300,8 @@ def add_horizontal_rule(self) -> HorizontalRule: .. doctest:: document >>> doc = snakemd.new_doc() - >>> _ = doc.add_horizontal_rule() + >>> doc.add_horizontal_rule() + >>> str(doc) '***' @@ -313,9 +324,12 @@ def add_table_of_contents(self, levels: range = range(2, 3)) -> TableOfContents: .. doctest:: document >>> doc = snakemd.new_doc() - >>> _ = doc.add_table_of_contents() - >>> _ = doc.add_heading("First Item", 2) - >>> _ = doc.add_heading("Second Item", 2) + >>> doc.add_table_of_contents() + + >>> doc.add_heading("First Item", 2) + + >>> doc.add_heading("Second Item", 2) + >>> str(doc) '1. [First Item](#first-item)\\n2. [Second Item](#second-item)\\n\\n## First Item\\n\\n## Second Item' @@ -339,7 +353,8 @@ def scramble(self) -> None: .. doctest:: document >>> doc = snakemd.new_doc() - >>> _ = doc.add_horizontal_rule() + >>> doc.add_horizontal_rule() + >>> doc.scramble() >>> str(doc) '***' @@ -358,7 +373,8 @@ def dump(self, name: str, dir: str | os.PathLike = "", ext: str = "md", encoding .. doctest:: document >>> doc = snakemd.new_doc() - >>> _ = doc.add_horizontal_rule() + >>> doc.add_horizontal_rule() + >>> doc.dump("README") :param str name: diff --git a/snakemd/elements.py b/snakemd/elements.py index 659ec27..9e89904 100644 --- a/snakemd/elements.py +++ b/snakemd/elements.py @@ -359,8 +359,8 @@ class Code(Block): :param str | Code code: the sourcecode to format as a Markdown code block - - set to a string which represents preformatted code (i.e., whitespace is respected) - - set to a Code object to nest an existing code block + - set to a string to render a preformatted code block (i.e., whitespace is respected) + - set to a Code object to render a nested code block :param str lang: the programming language for the code block; defaults to 'generic' """ @@ -423,11 +423,10 @@ class Heading(Block): :param str | Inline | Iterable[Inline | str] text: the heading text - - set to a string for unformatted heading text - - set to an Inline object to customize the overall styling of the - heading (e.g., bold, link, code, etc.) - - set to a "list" of the prior options to provide more granular - control over the individual inline elements in the heading + - set to a string to render raw heading text + - set to an Inline object to render a styled heading (e.g., bold, link, code, etc.) + - set to a "list" of the prior options to render a header with more granular + control over the individual inline elements :param int level: the heading level between 1 and 6 """ @@ -576,9 +575,9 @@ class MDList(Block): the checked state of the list - defaults to :code:`None` which excludes checkboxes from being rendered - - set to :code:`False` for a series of unchecked boxes (i.e., :code:`- [ ]`) - - set to :code:`True` for a series of checked boxes (i.e., :code:`- [x]`) - - set to :code:`Iterable[bool]` to configure the checked + - set to :code:`False` to render a series of unchecked boxes (i.e., :code:`- [ ]`) + - set to :code:`True` to render a series of checked boxes (i.e., :code:`- [x]`) + - set to :code:`Iterable[bool]` to render the checked status of the top-level list elements directly """ @@ -712,13 +711,13 @@ class Paragraph(Block): from snakemd import Paragraph - :param str | Iterable[Inline | str] content: + :param str | Iterable[str | Inline] content: the text to be rendered as a paragraph where whitespace is not respected (see :class:`snakemd.Raw` for whitespace sensitive applications) - - set to a string for a single line of unformatted text - - set to a "list" of text objects for more granular control of - the individual text objects within the paragraph (e.g., linking, + - set to a string to render a single line of unformatted text + - set to a "list" of text objects to render a paragraph with more + granular control over the individual text objects (e.g., linking, styling, etc.) """ @@ -915,8 +914,9 @@ class Quote(Block): :param str | Iterable[str | Inline | Block] content: the text to be formatted as a Markdown quote - - set to a string where whitespace is respected (similar to :class:`snakemd.Code`) - - set to a "list" of text objects which serve as individual lines of a quote + - set to a string to render a whitespace respected quote (similar to :class:`snakemd.Code`) + - set to a "list" of text objects to render a document-like quote + (i.e., all items will be separated by newlines) """ def __init__(self, content: str | Iterable[str | Inline | Block]) -> None: @@ -938,12 +938,12 @@ def _process_content(lines) -> list[Block]: """ logger.debug(f"Processing quote lines: {lines}") if isinstance(lines, str): - processed_lines = [Paragraph(lines)] + processed_lines = [Raw(lines)] else: processed_lines = [] for line in lines: if isinstance(line, (str, Inline)): - processed_lines.append(Paragraph(line)) + processed_lines.append(Raw(line)) else: processed_lines.append(line) return processed_lines @@ -1188,7 +1188,8 @@ def add_row(self, row: Iterable[str | Inline | Paragraph]) -> Table: .. doctest:: table >>> table = Table(["Rank", "Player"], [["1st", "Crosby"], ["2nd", "McDavid"]]) - >>> _ = table.add_row(["3rd", "Matthews"]) + >>> table.add_row(["3rd", "Matthews"]) + >>> str(table) '| Rank | Player |\\n| ---- | -------- |\\n| 1st | Crosby |\\n| 2nd | McDavid |\\n| 3rd | Matthews |' diff --git a/tests/test_quote.py b/tests/test_quote.py index cc11023..fa1e132 100644 --- a/tests/test_quote.py +++ b/tests/test_quote.py @@ -5,6 +5,11 @@ def test_quote_one_str(): quote = Quote("Single Phrase") assert str(quote) == "> Single Phrase" + +def test_quote_one_str_formatted(): + quote = Quote("Single Phrase\n\tLet's go!") + assert str(quote) == "> Single Phrase\n> \tLet's go!" + def test_quote_multiple_lines(): quote = Quote(["First", "Second", "Third"])