From e6593e7db2c0c520c694411cf757abcde8d4c614 Mon Sep 17 00:00:00 2001 From: Kyle Bowman Date: Sun, 14 Apr 2024 22:11:11 -0400 Subject: [PATCH] Add render_files and tests. --- pyproject.toml | 2 +- src/krass/__init__ | 0 src/krass/render.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ tests/data/test.md | 26 ++++++++++++++++++++++++++ tests/test_render.py | 37 +++++++++++++++++++++++++++++++++++++ todo.md | 27 +++++++++++++++++++++++++++ 6 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 src/krass/__init__ create mode 100644 src/krass/render.py create mode 100644 tests/data/test.md create mode 100644 tests/test_render.py create mode 100644 todo.md diff --git a/pyproject.toml b/pyproject.toml index 9de00c2..08dd4e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ classifiers = [ ] [project.scripts] -render = "render:main" +render = "krass.render:main" [project.urls] Homepage = "None" diff --git a/src/krass/__init__ b/src/krass/__init__ new file mode 100644 index 0000000..e69de29 diff --git a/src/krass/render.py b/src/krass/render.py new file mode 100644 index 0000000..c58dcc7 --- /dev/null +++ b/src/krass/render.py @@ -0,0 +1,44 @@ +import os +from pathlib import Path +from typing import Optional + +from markdown_it import MarkdownIt + +from proto import command, get_argspecs +from proto.io import Stdio + + +def render_contents(contents: str)->str: + return MarkdownIt().render(contents) + +@command +def render_file(infile: Optional[Path]=None, outfile: Optional[Path]=None): + file_contents = Stdio.read(infile) + output = render_contents(file_contents) + Stdio.write(output, outfile) + +@get_argspecs.register +def bool_argspecs(annotation: bool)->dict: + """ Implements get_argspecs for Booleans. """ + return {'action':'store_true'} + +@get_argspecs.register +def list_argspecs(annotation: list)->dict: + """ Implements argspecs for a list of one or more items.""" + return {'nargs':'+', 'type':Path} + +@command +def render_files(files: list, norecursion: bool = False): + for path in files: + if path.exists and path.is_file(): + outfile= Path(path.parent).joinpath(path.stem + ".html") + render_file(infile=path, outfile=outfile) + elif path.exists and path.is_dir() and not norecursion: + children = [path.joinpath(child) for child in os.listdir(path)] + render_files(files=children) + else: + raise Exception(f"The specified path does not exist or is not a file: {path}.") + +def main(): + render_files.parse() + render_files.run() diff --git a/tests/data/test.md b/tests/data/test.md new file mode 100644 index 0000000..cc1c075 --- /dev/null +++ b/tests/data/test.md @@ -0,0 +1,26 @@ +# Title + +This is a paragraph. + +* Unordered item one +* Unordered item two + +``` python +print("This is code") +``` + +## Subtitle + +This line has *emphasized* text. + +This line has **strong** text. + +This line has `inline code`. + +1. Enumerated one +2. Enumerated two + +| Column One | Column Two | +| ---------- | ---------- | +| Entry 1, 1 | Entry 1, 2 | +| Entry 2, 1 | Entry 2, 2 | \ No newline at end of file diff --git a/tests/test_render.py b/tests/test_render.py new file mode 100644 index 0000000..3e14bd1 --- /dev/null +++ b/tests/test_render.py @@ -0,0 +1,37 @@ +import os +import shlex +import shutil +from pathlib import Path + +import pytest + +from krass.render import render_file, render_files + +DATA = Path.cwd().joinpath("tests/data/test.md") + +# TODO: When you create outdir option, rewrite tests to write to tmp rather than copying. +# TODO: Test --norecursion + +def test_single_file(tmp_path): + infile = Path(shutil.copy(DATA, tmp_path)) + outfile = Path(tmp_path).joinpath(infile.stem + ".html") + render_file.parse(args=shlex.split(f"--infile {infile} --outfile={outfile}")) + render_file.run() + contents = os.listdir(tmp_path) + assert outfile.name in contents + +def test_two_files(tmp_path): + infile1 = Path(shutil.copy(DATA, tmp_path)) + infile2 = Path(shutil.copy(DATA, tmp_path.joinpath("test2.md"))) + outfile1 = Path(tmp_path).joinpath(infile1.stem + ".html") + outfile2 = Path(tmp_path).joinpath(infile2.stem + ".html") + render_files.parse(args=shlex.split(f"{infile1} {infile2}")) + render_files.run() + contents = os.listdir(tmp_path) + assert outfile1.name in contents + assert outfile2.name in contents + +def test_non_file(): + with pytest.raises(Exception): + render_files.parse(args=shlex.split("/not/a/path")) + render_files.run() diff --git a/todo.md b/todo.md new file mode 100644 index 0000000..bc6143f --- /dev/null +++ b/todo.md @@ -0,0 +1,27 @@ +# TODO: + +## Krass +* Add --outdir to render_files to separate A +* With --outdir, clean up tests +* BUG: In render_files: list should be list[Path] See proto.infer._parse_type bug + * Note: It doesn't look like you can dispatch on list[T] for various T. + * Per https://stackoverflow.com/questions/68381197/is-it-possible-to-use-functools-singledispatch-with-composite-nested-container-t + * I wonder if you can get around that by singledispatch currying + * e.g. dispatch on list to a generic function that is itself dispatched on T. +* Clean up Exception raised in render_files. Make specific and push upstream. + +## Proto +* Refine Stdio stuff. (Binding infile to content, define hook in command decorator) + * This will probably be done under the hood with functools.partial. + * Turn this into a context manager? + ``` python + with Stdio: + do_command(*args, **kwargs) + ``` + * Still need a way to bind infile_contents -> do_command argument and otherwise pass args. + * `@command(io=Stdio, infile="contents")` + * Alt: `@command(io=Stdio(infile="contents"))` +* Continue work with subparsers branch +* Consider defining walk(predicate, function) as a way to render files. +* Add Stdio check for Path.exists and Path.is_file +* Add Stdio tests? \ No newline at end of file -- 2.39.5