Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CLI option to render package README. #271

Merged
merged 5 commits into from
Jan 5, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 53 additions & 7 deletions readme_renderer/__main__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,65 @@
import argparse
from readme_renderer.rst import render
import email
from readme_renderer.markdown import render as render_md
from readme_renderer.rst import render as render_rst
from readme_renderer.txt import render as render_txt
import pathlib
from pkg_resources import get_distribution
import sys


if __name__ == '__main__':
def __main__(args=None): # noqa: N807
miketheman marked this conversation as resolved.
Show resolved Hide resolved
parser = argparse.ArgumentParser(
description="Renders a .rst README to HTML",
description="Renders a .md, .rst, or .txt README to HTML",
)
parser.add_argument('input', help="Input README file",
type=argparse.FileType('r'))
parser.add_argument("-p", "--package", help="Get README from package metadata",
action="store_true")
parser.add_argument("-f", "--format", choices=["md", "rst", "txt"],
help="README format (inferred from input file name or package)")
parser.add_argument('input', help="Input README file or package name")
parser.add_argument('-o', '--output', help="Output file (default: stdout)",
type=argparse.FileType('w'), default='-')
args = parser.parse_args()
args = parser.parse_args(args)

content_format = args.format
if args.package:
distribution = get_distribution(args.input)
pkg_info = distribution.get_metadata(distribution.PKG_INFO)
message = email.message_from_string(pkg_info)
source = message.get_payload()

# Infer the format of the description from package metadata.
if not content_format:
content_type = message.get("Description-Content-Type", "text/x-rst")
if content_type == "text/x-rst":
content_format = "rst"
elif content_type == "text/markdown":
content_format = "md"
elif content_type == "text/plain":
content_format = "txt"
else:
raise ValueError(f"invalid content type {content_type} for package "
"`long_description`")
Comment on lines +33 to +43
Copy link
Contributor Author

@tillahoffmann tillahoffmann Jan 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part isn't particularly well tested because we'd have to install a bunch of dummy packages for the tests to run. Or we could mock out get_distribution to return the right data (not quite as reliable a test if we get the mocking wrong).

else:
filename = pathlib.Path(args.input)
content_format = content_format or filename.suffix.lstrip(".")
with filename.open() as fp:
source = fp.read()

rendered = render(args.input.read(), stream=sys.stderr)
if content_format == "md":
render = render_md
elif content_format == "rst":
render = render_rst
elif content_format == "txt":
render = render_txt
else:
raise ValueError(f"invalid README format: {content_format} (expected `md`, "
"`rst`, or `txt`)")
rendered = render(source, stream=sys.stderr)
if rendered is None:
sys.exit(1)
print(rendered, file=args.output)


if __name__ == '__main__':
__main__()
61 changes: 61 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import pathlib
import pytest
from readme_renderer.__main__ import __main__
import tempfile
from unittest import mock


@pytest.fixture(params=["test_CommonMark_001.md", "test_rst_003.rst",
"test_GFM_001.md", "test_txt_001.txt"])
def input_file(request):
return pathlib.Path("tests/fixtures", request.param)


@pytest.mark.parametrize("output_file", [False, True])
def test_cli_input_file(input_file, output_file):
if output_file:
with tempfile.TemporaryDirectory() as tmpdir:
output = pathlib.Path(tmpdir) / "output.html"
__main__(["-o", str(output), str(input_file)])
with output.open() as fp:
result = fp.read()
else:
with mock.patch("builtins.print") as print_:
__main__([str(input_file)])
print_.assert_called_once()
(result,), _ = print_.call_args

with input_file.with_suffix(".html").open() as fp:
expected = fp.read()
assert result.strip() == expected.strip()


def test_cli_invalid_format():
with mock.patch("pathlib.Path.open"), \
pytest.raises(ValueError, match="invalid README format: invalid"):
__main__(["no-file.invalid"])


def test_cli_explicit_format(input_file):
fmt = input_file.suffix.lstrip(".")
with input_file.open() as fp, \
mock.patch("pathlib.Path.open", return_value=fp), \
mock.patch("builtins.print") as print_:
__main__(["-f", fmt, "no-file.invalid"])
print_.assert_called_once()
(result,), _ = print_.call_args

with input_file.with_suffix(".html").open() as fp:
assert result.strip() == fp.read().strip()


@pytest.mark.parametrize("package, contains", [
("readme_renderer", "Readme Renderer is a library that will safely render"),
("docutils", "Docutils is a modular system for processing documentation"),
])
def test_cli_package(package, contains):
with mock.patch("builtins.print") as print_:
__main__(["-p", package])
print_.assert_called_once()
(result,), _ = print_.call_args
assert contains in result