From ec3fda86568fa3018d0bfdb5f31d9cf132d258e4 Mon Sep 17 00:00:00 2001 From: Richard Alpe Date: Tue, 16 Sep 2025 16:20:49 +0200 Subject: [PATCH] report: incorporate pdf generation and simplify arguments And add makefile targets to generate a report from last run. Signed-off-by: Richard Alpe --- .github/workflows/main.yml | 10 +-- Makefile | 35 ++++++---- report.py | 139 ++++++++++++++++++++++++++++--------- 3 files changed, 134 insertions(+), 50 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 07db124..41f6eb0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -43,14 +43,14 @@ jobs: python3 -m json.tool ~/.local/share/9pm/logs/last/result.json >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY - - name: Generate Reports from JSON - run: python3 report.py ~/.local/share/9pm/logs/last/result.json all -o ~/.local/share/9pm/logs/last/ + - name: Generate GitHub Test Result + run: make report-github - - name: Publish GitHub Test Result + - name: Publish GitHub Report run: cat ~/.local/share/9pm/logs/last/result-gh.md >> $GITHUB_STEP_SUMMARY - - name: Generate PDF Report - run: make report + - name: Generate PDF Test Report + run: make report-pdf - name: Upload Logs as Artifacts uses: actions/upload-artifact@v4 diff --git a/Makefile b/Makefile index e14104f..a186791 100644 --- a/Makefile +++ b/Makefile @@ -14,10 +14,14 @@ help: @echo "9pm Makefile - For developers of 9pm itself" @echo @echo "Available targets:" - @echo " check - Run self-tests to verify 9pm functionality" - @echo " test - Run unit tests with cmdl-supplied option" - @echo " report - Generate all reports (markdown, asciidoc, PDF) from JSON" - @echo " help - Show this help message" + @echo " check - Run self-tests to verify 9pm functionality" + @echo " test - Run unit tests with cmdl-supplied option" + @echo " report-github - Generate GitHub markdown report from last test results" + @echo " report-markdown - Generate plain markdown report from last test results" + @echo " report-asciidoc - Generate AsciiDoc report from last test results" + @echo " report-pdf - Generate PDF report from last test results" + @echo " report - Alias for report-pdf (legacy)" + @echo " help - Show this help message" @echo @echo "Variables:" @echo " TEST=$(TEST) - Specify test results to use for report (default: last)" @@ -36,13 +40,18 @@ test: --option cmdl-supplied \ unit_tests/all.yaml -# Generate reports from JSON results -report: - python3 report.py $(TESTPATH)/result.json all -o $(TESTPATH) - asciidoctor-pdf --theme "$(THEME)" \ - -a pdf-fontsdir=report/fonts \ - -a logo="image:$(LOGO)" \ - -o $(TESTPATH)/report.pdf \ - $(TESTPATH)/report.adoc +report-github: + ./report.py github -.PHONY: all check test report help +report-markdown: + ./report.py markdown + +report-asciidoc: + ./report.py asciidoc + +report-pdf: + ./report.py pdf + +report: report-pdf + +.PHONY: all check test report report-github report-markdown report-asciidoc report-pdf help diff --git a/report.py b/report.py index d240145..f282520 100755 --- a/report.py +++ b/report.py @@ -4,6 +4,7 @@ import argparse import sys import os +import subprocess from datetime import datetime from abc import ABC, abstractmethod @@ -23,6 +24,32 @@ def generate(self) -> str: """Generate the report content.""" pass + @abstractmethod + def get_filename(self): + """Get the output filename for this report type.""" + pass + + def _get_output_filename(self, json_file_path, output_filename): + """Get the final output filename.""" + if output_filename: + return output_filename + json_dir = os.path.dirname(os.path.abspath(json_file_path)) + return os.path.join(json_dir, self.get_filename()) + + def write(self, json_file_path, output_filename=None): + """Generate content and write to file.""" + content = self.generate() + filename = self._get_output_filename(json_file_path, output_filename) + + try: + with open(filename, 'w') as f: + f.write(content) + print(f"Generated {filename}") + return filename + except IOError as e: + print(f"Error writing {filename}: {e}", file=sys.stderr) + sys.exit(1) + def get_result_counts(self): """Get summary statistics for results.""" return { @@ -38,6 +65,10 @@ def get_result_counts(self): class AsciiDocReporter(BaseReporter): """Generates AsciiDoc format reports.""" + def get_filename(self): + """Get the output filename for this report type.""" + return 'report.adoc' + def generate(self) -> str: """Generate AsciiDoc report.""" content = [] @@ -220,6 +251,10 @@ def _write_output_sections(self, data, depth, is_first=True): class GitHubMarkdownReporter(BaseReporter): """Generates GitHub-flavored Markdown with emoji icons.""" + def get_filename(self): + """Get the output filename for this report type.""" + return 'result-gh.md' + def generate(self) -> str: """Generate GitHub Markdown report.""" content = ["# Test Result", ""] @@ -254,6 +289,10 @@ def _write_tree(self, data, depth): class PlainMarkdownReporter(BaseReporter): """Generates plain Markdown format.""" + def get_filename(self): + """Get the output filename for this report type.""" + return 'result.md' + def generate(self) -> str: """Generate plain Markdown report.""" content = ["# Test Result", ""] @@ -277,6 +316,54 @@ def _write_tree(self, data, depth): return content +class PDFReporter(BaseReporter): + """Generates PDF reports via AsciiDoc conversion.""" + + def get_filename(self): + """Get the output filename for this report type.""" + return 'report.pdf' + + def generate(self) -> str: + """Generate AsciiDoc content (same as AsciiDocReporter).""" + return AsciiDocReporter(self.data).generate() + + def write(self, json_file_path, output_filename=None): + """Generate AsciiDoc content and convert to PDF using temp file.""" + import tempfile + + content = self.generate() + pdf_filename = self._get_output_filename(json_file_path, output_filename) + + with tempfile.NamedTemporaryFile(mode='w', suffix='.adoc', delete=False) as temp_adoc: + temp_adoc.write(content) + temp_adoc_path = temp_adoc.name + + try: + script_dir = os.path.dirname(os.path.abspath(__file__)) + cmd = ['asciidoctor-pdf', + '--theme', os.path.join(script_dir, 'report', 'theme.yml'), + '-a', f'pdf-fontsdir={os.path.join(script_dir, "report", "fonts")}', + '-a', f'logo=image:{os.path.join(script_dir, "logo.png")}[top=40%, align=right, pdfwidth=8cm]', + '-o', pdf_filename, temp_adoc_path] + + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode == 0: + print(f"Generated {pdf_filename}") + return pdf_filename + else: + print(f"Error generating PDF: {result.stderr}", file=sys.stderr) + sys.exit(1) + + except FileNotFoundError: + print("Error: asciidoctor-pdf not found. Install with: gem install asciidoctor-pdf", file=sys.stderr) + sys.exit(1) + except Exception as e: + print(f"Error running asciidoctor-pdf: {e}", file=sys.stderr) + sys.exit(1) + finally: + os.unlink(temp_adoc_path) + + def load_json_data(json_file): """Load and parse the JSON result file.""" try: @@ -293,45 +380,33 @@ def load_json_data(json_file): def main(): """Main CLI interface.""" parser = argparse.ArgumentParser(description='Generate test reports from JSON data') - parser.add_argument('json_file', help='Path to the JSON result file') - parser.add_argument('format', choices=['github', 'markdown', 'asciidoc', 'all'], + parser.add_argument('format', choices=['github', 'markdown', 'asciidoc', 'pdf'], help='Report format to generate') - parser.add_argument('-o', '--output-dir', default='.', - help='Output directory for generated reports (default: current directory)') + parser.add_argument('json_file', nargs='?', help='Path to the JSON result file (auto-find if omitted)') + parser.add_argument('-o', '--output', help='Output filename (auto-generate if omitted)') args = parser.parse_args() + # Auto-find JSON file if not provided + if args.json_file is None: + args.json_file = os.path.expanduser('~/.local/share/9pm/logs/last/result.json') + # Load JSON data json_data = load_json_data(args.json_file) - # Generate reports based on format - if args.format == 'all': - formats = ['github', 'markdown', 'asciidoc'] - else: - formats = [args.format] - - for fmt in formats: - if fmt == 'github': - reporter = GitHubMarkdownReporter(json_data) - content = reporter.generate() - filename = os.path.join(args.output_dir, 'result-gh.md') - elif fmt == 'markdown': - reporter = PlainMarkdownReporter(json_data) - content = reporter.generate() - filename = os.path.join(args.output_dir, 'result.md') - elif fmt == 'asciidoc': - reporter = AsciiDocReporter(json_data) - content = reporter.generate() - filename = os.path.join(args.output_dir, 'report.adoc') - - # Write output file - try: - with open(filename, 'w') as f: - f.write(content) - print(f"Generated {filename}") - except IOError as e: - print(f"Error writing {filename}: {e}", file=sys.stderr) - sys.exit(1) + # Generate report based on format + try: + if args.format == 'github': + GitHubMarkdownReporter(json_data).write(args.json_file, args.output) + elif args.format == 'markdown': + PlainMarkdownReporter(json_data).write(args.json_file, args.output) + elif args.format == 'asciidoc': + AsciiDocReporter(json_data).write(args.json_file, args.output) + elif args.format == 'pdf': + PDFReporter(json_data).write(args.json_file, args.output) + except Exception as e: + print(f"Error generating report: {e}", file=sys.stderr) + sys.exit(1) if __name__ == '__main__':