diff --git a/snakemd/elements.py b/snakemd/elements.py index 04733c6..c968875 100644 --- a/snakemd/elements.py +++ b/snakemd/elements.py @@ -37,12 +37,11 @@ def __str__(self) -> str: def __repr__(self) -> str: """ The developer's string method to help make sense of - objects. As described by Digital Ocean, this method should - return a string that can be used to recreate the object. - This will not be true for every possible element as - there are internal structures as a result of - post-processing, but it should be more informative - than the default __repr__ method. Ultimately, this + objects. For the purposes of this repo, the __repr__ + method should create strings that can be used to + recreate the element, much like the built-in + feature of dataclasses (a feature which may be adopted + in future versions of snakemd). Ultimately, this method must be implemented by all inheriting classes. :return: diff --git a/snakemd/templates.py b/snakemd/templates.py index 2addf6d..04609e2 100644 --- a/snakemd/templates.py +++ b/snakemd/templates.py @@ -5,9 +5,11 @@ from __future__ import annotations +import csv import logging +import os -from .elements import Element, Heading, Inline, MDList +from .elements import Element, Heading, Inline, MDList, Table logger = logging.getLogger(__name__) @@ -50,6 +52,76 @@ def load(self, elements: list[Element]) -> None: self._elements = elements +class CSVTable(Template): + """ + A CSV Table is a wrapper for the Table Block, + which provides a seamless way to load CSV data + into Markdown. Because Markdown tables are + required to have headers, the first row of + the CSV is assumed to be a header. Future + iterations of this template may allow users + to select the exact row for their header. + Future iterations may also allow for different + CSV dialects like Excel. + + .. versionadded:: 2.2 + Included to showcase the possibilities of + templates + + :param os.Pathlike path: + the path to a CSV file + :param str encoding: + the encoding of the CSV file; defaults to utf-8 + """ + + def __init__(self, path: os.PathLike, encoding: str = "utf-8") -> None: + super().__init__() + self._path = path + self._encoding = encoding + self._table = self._process_csv(path, encoding) + + def __str__(self) -> str: + """ + Renders self as a markdown ready string. See + :class:`snakemd.Table` for more details. + + :return: + the CSVTable as a markdown string + """ + return str(self._table) + + def __repr__(self) -> str: + """ + Renders self as an unambiguous string for development. + See :class:`snakemd.Table` for more details. + + :return: + the CSVTable as a development string + """ + return repr(self._table) + + @staticmethod + def _process_csv(path: os.PathLike, encoding: str) -> Table: + """ + A helper method for processing the CSV file into + a Table object. + + :param os.Pathlike path: + the path to the CSV file + :param str encoding: + the encoding of the CSV file + :return: + the CSV file as a markdown Table + """ + with open(path, encoding=encoding) as csv_file: + csv_reader = csv.reader(csv_file) + header = next(csv_reader) + table = Table(header=header) + for row in csv_reader: + table.add_row(row=row) + return table + + class TableOfContents(Template): """ A Table of Contents is an element containing an ordered list @@ -65,14 +137,15 @@ class TableOfContents(Template): to include in the table of contents; defaults to range(2, 3) """ - def __init__(self, levels: range = range(2, 3)): + def __init__(self, levels: range = range(2, 3)) -> None: super().__init__() self._levels: range = levels logger.debug("New table of contents initialized with levels in %s", levels) def __str__(self) -> str: """ - Renders the table of contents using the Document reference. + Renders self as a markdown ready string. See :class:`snakemd.MDList` + for more details. :return: the table of contents as a markdown string diff --git a/tests/resources/python-support.csv b/tests/resources/python-support.csv new file mode 100644 index 0000000..d833fbc --- /dev/null +++ b/tests/resources/python-support.csv @@ -0,0 +1,4 @@ +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 diff --git a/tests/resources/python-support.md b/tests/resources/python-support.md new file mode 100644 index 0000000..f948bfd --- /dev/null +++ b/tests/resources/python-support.md @@ -0,0 +1,5 @@ +| 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 | \ No newline at end of file diff --git a/tests/templates/test_csv_table.py b/tests/templates/test_csv_table.py new file mode 100644 index 0000000..27e9594 --- /dev/null +++ b/tests/templates/test_csv_table.py @@ -0,0 +1,31 @@ +import pytest + +from snakemd.templates import CSVTable + + +def test_csv_table_type_error_none(): + """ + Verifies that None properly throws a TypeError. + """ + with pytest.raises(TypeError): + CSVTable(None) + + +def test_csv_table_file_not_found_error_empty(): + """ + Verifies that an empty string properly throws FileNotFoundError. + """ + with pytest.raises(FileNotFoundError): + CSVTable("") + +def test_csv_table_sample_file(): + """ + Verifies that a sample CSV is properly converted into + a Markdown table. + """ + table = CSVTable("tests/resources/python-support.csv") + table_lines = str(table).splitlines(keepends=True) + with open("tests/resources/python-support.md") as md: + for actual, expected in zip(md.readlines(), table_lines): + assert actual == expected +