Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,12 @@ Render markdown to HTML with markdown-it-py from the
command-line:

```console
usage: markdown-it [-h] [-v] [filenames [filenames ...]]
usage: markdown-it [-h] [-v] [--stdin|filenames [filenames ...]]

Parse one or more markdown files, convert each to HTML, and print to stdout

positional arguments:
--stdin read source Markdown file from standard input
filenames specify an optional list of files to convert

optional arguments:
Expand Down
17 changes: 17 additions & 0 deletions markdown_it/cli/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ def main(args: Sequence[str] | None = None) -> int:
namespace = parse_args(args)
if namespace.filenames:
convert(namespace.filenames)
elif namespace.stdin:
convert_stdin()
else:
interactive()
return 0
Expand All @@ -31,6 +33,18 @@ def convert(filenames: Iterable[str]) -> None:
convert_file(filename)


def convert_stdin() -> None:
"""
Parse a Markdown file and dump the output to stdout.
"""
try:
rendered = MarkdownIt().render(sys.stdin.read())
print(rendered, end="")
except OSError:
sys.stderr.write("Cannot parse Markdown from the standard input.\n")
sys.exit(1)


def convert_file(filename: str) -> None:
"""
Parse a Markdown file and dump the output to stdout.
Expand Down Expand Up @@ -94,6 +108,9 @@ def parse_args(args: Sequence[str] | None) -> argparse.Namespace:
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument("-v", "--version", action="version", version=version_str)
parser.add_argument(
"--stdin", action="store_true", help="read Markdown from standard input"
)
parser.add_argument(
"filenames", nargs="*", help="specify an optional list of files to convert"
)
Expand Down
55 changes: 55 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from contextlib import redirect_stdout
import io
import pathlib
import tempfile
from unittest.mock import patch
Expand Down Expand Up @@ -40,3 +42,56 @@ def mock_input(prompt):
with patch("builtins.print") as patched, patch("builtins.input", mock_input):
parse.interactive()
patched.assert_called()


def test_main_no_args_is_interactive():
with patch("markdown_it.cli.parse.interactive") as mock_interactive:
assert parse.main([]) == 0
mock_interactive.assert_called_once()


def test_parse_output():
with tempfile.TemporaryDirectory() as tempdir:
path = pathlib.Path(tempdir).joinpath("test.md")
path.write_text("# a b c")
string_io = io.StringIO()
with redirect_stdout(string_io):
assert parse.main([str(path)]) == 0
assert string_io.getvalue() == "<h1>a b c</h1>\n"


def test_stdin():
with patch("sys.stdin", io.StringIO("# a b c")):
string_io = io.StringIO()
with redirect_stdout(string_io):
assert parse.main(["--stdin"]) == 0
assert string_io.getvalue() == "<h1>a b c</h1>\n"


def test_multiple_files():
with tempfile.TemporaryDirectory() as tempdir:
path1 = pathlib.Path(tempdir).joinpath("test1.md")
path1.write_text("# file 1")
path2 = pathlib.Path(tempdir).joinpath("test2.md")
path2.write_text("* file 2")
string_io = io.StringIO()
with redirect_stdout(string_io):
assert parse.main([str(path1), str(path2)]) == 0
assert string_io.getvalue() == "<h1>file 1</h1>\n<ul>\n<li>file 2</li>\n</ul>\n"


def test_interactive_render():
# Simulate user typing '# hello', pressing Ctrl-D (renders), then Ctrl-C (exits)
# This is needed to break the infinite loop in interactive mode on EOF.
mock_input = patch(
"builtins.input", side_effect=["# hello", EOFError, KeyboardInterrupt]
)
string_io = io.StringIO()
with redirect_stdout(string_io), mock_input:
parse.interactive()

output = string_io.getvalue()
assert "markdown-it-py" in output # from print_heading
# The rendered output is prefixed by a newline
assert "\n<h1>hello</h1>\n" in output
assert "Exiting" in output