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 all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
60 changes: 53 additions & 7 deletions readme_renderer/__main__.py
@@ -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
from typing import Optional, List


if __name__ == '__main__':
def main(cli_args: Optional[List[str]] = None) -> None:
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(cli_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

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":
rendered = render_md(source, stream=sys.stderr)
elif content_format == "rst":
rendered = render_rst(source, stream=sys.stderr)
elif content_format == "txt":
rendered = render_txt(source, stream=sys.stderr)
else:
raise ValueError(f"invalid README format: {content_format} (expected `md`, "
"`rst`, or `txt`)")
if rendered is None:
sys.exit(1)
print(rendered, file=args.output)


if __name__ == '__main__':
main()
65 changes: 65 additions & 0 deletions tests/test_cli.py
@@ -0,0 +1,65 @@
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):
with mock.patch("builtins.print") as print_:
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:
main([str(input_file)])

print_.assert_called_once()
(result,), kwargs = print_.call_args

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

if output_file:
assert kwargs["file"].name == str(output)


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