Skip to content

Commit

Permalink
Added CSVTable Template (#153)
Browse files Browse the repository at this point in the history
* Too easy!

* Rewrote the __repr__ contract

* Adding docs for CSVTable

* Adding docs

* Added more docs

* Finished documenting new class

* Added tests

* Added docs to tests

* Added empty line
  • Loading branch information
jrg94 authored May 3, 2023
1 parent 388ed6d commit 38ab0a3
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 9 deletions.
11 changes: 5 additions & 6 deletions snakemd/elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
79 changes: 76 additions & 3 deletions snakemd/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions tests/resources/python-support.csv
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions tests/resources/python-support.md
Original file line number Diff line number Diff line change
@@ -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 |
31 changes: 31 additions & 0 deletions tests/templates/test_csv_table.py
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 38ab0a3

Please sign in to comment.