From b3143e9d0255c3afc0eff7e74beeb08216d111ec Mon Sep 17 00:00:00 2001 From: Alexander Ryabov Date: Tue, 15 Sep 2026 17:20:11 +0300 Subject: [PATCH] Add command-line JSON output --- changelog.d/20260914_aryabov_cli.rst | 4 ++ feedparser/cli.py | 27 ++++++++++++++ pyproject.toml | 3 ++ tests/test_cli.py | 55 ++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+) create mode 100644 changelog.d/20260914_aryabov_cli.rst create mode 100644 feedparser/cli.py create mode 100644 tests/test_cli.py diff --git a/changelog.d/20260914_aryabov_cli.rst b/changelog.d/20260914_aryabov_cli.rst new file mode 100644 index 00000000..8bbb86e8 --- /dev/null +++ b/changelog.d/20260914_aryabov_cli.rst @@ -0,0 +1,4 @@ +Added +----- + +* Add a command-line tool that outputs normalized feed data as JSON. diff --git a/feedparser/cli.py b/feedparser/cli.py new file mode 100644 index 00000000..3f486828 --- /dev/null +++ b/feedparser/cli.py @@ -0,0 +1,27 @@ +import argparse +import json +import sys + +from .api import parse + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("source", help="feed URL, filename, or - for stdin") + args = parser.parse_args() + + source = sys.stdin.buffer if args.source == "-" else args.source + feed = parse(source) + if feed.bozo: + raise feed.bozo_exception + + json.dump( + {"feed": feed.feed, "entries": feed.entries}, + sys.stdout, + ensure_ascii=False, + indent=2, + ) + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml index fae60798..8aea144b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,9 @@ dependencies = [ "requests>=2.20.0", ] +[project.scripts] +feedparser = "feedparser.cli:main" + [project.urls] Source = "https://github.com/kurtmckee/feedparser" Documentation = "https://feedparser.readthedocs.io/en/latest/" diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 00000000..dfb77d12 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,55 @@ +import json +import subprocess +import sys + +import pytest + +FEED = """\ + + + + Test feed + https://example.com/ + Test feed + + Café + https://example.com/cafe + + + +""" + + +@pytest.mark.parametrize("from_stdin", [False, True]) +def test_cli_outputs_entries(tmp_path, from_stdin): + path = tmp_path / "feed.xml" + path.write_text(FEED) + source = "-" if from_stdin else str(path) + + result = subprocess.run( + [sys.executable, "-m", "feedparser.cli", source], + input=FEED if from_stdin else None, + capture_output=True, + text=True, + ) + + assert result.returncode == 0 + output = json.loads(result.stdout) + assert output["feed"]["title"] == "Test feed" + assert output["entries"][0]["title"] == "Café" + assert "Café" in result.stdout + assert '\n "feed": {' in result.stdout + assert result.stderr == "" + + +def test_cli_fails_on_malformed_feed(): + result = subprocess.run( + [sys.executable, "-m", "feedparser.cli", "-"], + input="", + capture_output=True, + text=True, + ) + + assert result.returncode != 0 + assert result.stdout == "" + assert result.stderr