diff --git a/README.md b/README.md index 1b73f1f..46408cc 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,7 @@ Writing a PR description after the coding is finished is easy to postpone. diffn ## Quick start -```bash -git clone https://github.com/LucasXlab/diffnote.git && cd diffnote -python3 -m pip install . -python3 scripts/demo.py -``` + ## Features @@ -80,12 +76,6 @@ python3 -m pytest -q The demo uses a temporary fixture and does not inspect your personal repositories or agent homes. -## Roadmap - -- [ ] Improve error messages for invalid refs and empty histories. -- [ ] Add configurable grouping rules. - -## Contributing Read [CONTRIBUTING.md](CONTRIBUTING.md). Report reproducible problems in [Issues](https://github.com/LucasXlab/diffnote/issues). Follow [SECURITY.md](SECURITY.md) for sensitive reports. diff --git a/src/diffnote/cli.py b/src/diffnote/cli.py index 6d0fd7b..e9907cc 100644 --- a/src/diffnote/cli.py +++ b/src/diffnote/cli.py @@ -28,6 +28,8 @@ def build_parser(): fmt = p.add_argument_group("output") fmt.add_argument("--markdown", action="store_true", help="Print a GitHub PR-body markdown.") + fmt.add_argument("--save", metavar="PATH", + help="Write markdown output to PATH instead of stdout (requires --markdown).") fmt.add_argument("--json", action="store_true", help="Print machine-readable JSON.") color = fmt.add_mutually_exclusive_group() @@ -55,7 +57,10 @@ def get_diff(args, cwd): def main(argv=None): - args = build_parser().parse_args(argv) + parser = build_parser() + args = parser.parse_args(argv) + if args.save and (not args.markdown or args.json): + parser.error("--save requires --markdown and cannot be used with --json") try: cwd = gitutil.repo_root() except gitutil.GitError as exc: @@ -89,7 +94,16 @@ def main(argv=None): if args.json: print(render.render_json(summary, llm_prose)) elif args.markdown: - print(render.render_markdown(summary, llm_prose)) + markdown = render.render_markdown(summary, llm_prose) + if args.save: + try: + with open(args.save, "w", encoding="utf-8", newline="\n") as output_file: + output_file.write(markdown + "\n") + except OSError as exc: + print(f"diffnote: could not write {args.save}: {exc}", file=sys.stderr) + return 1 + else: + print(markdown) else: print(render.render_terminal(summary, use_color=args.use_color, llm_prose=llm_prose)) return 0 diff --git a/tests/test_cli.py b/tests/test_cli.py index 62201d7..4941bae 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -93,6 +93,25 @@ def test_markdown_output(capsys): assert "## Changes" in out +def test_save_markdown_writes_file_without_stdout(capsys): + d = _make_dirty_repo() + output_path = os.path.join(d, "pr-body.md") + old = os.getcwd() + os.chdir(d) + try: + rc = cli.main(["--save", output_path, "--markdown"]) + finally: + os.chdir(old) + captured = capsys.readouterr() + with open(output_path, encoding="utf-8") as output_file: + saved = output_file.read() + assert rc == 0 + assert captured.out == "" + assert "## Summary" in saved + assert "## Changes" in saved + assert saved.endswith("\n") + + def test_no_changes(capsys): import os d = tempfile.mkdtemp()