44import argparse
55import sys
66import os
7+ import subprocess
78from datetime import datetime
89from abc import ABC , abstractmethod
910
@@ -23,6 +24,32 @@ def generate(self) -> str:
2324 """Generate the report content."""
2425 pass
2526
27+ @abstractmethod
28+ def get_filename (self ):
29+ """Get the output filename for this report type."""
30+ pass
31+
32+ def _get_output_filename (self , json_file_path , output_filename ):
33+ """Get the final output filename."""
34+ if output_filename :
35+ return output_filename
36+ json_dir = os .path .dirname (os .path .abspath (json_file_path ))
37+ return os .path .join (json_dir , self .get_filename ())
38+
39+ def write (self , json_file_path , output_filename = None ):
40+ """Generate content and write to file."""
41+ content = self .generate ()
42+ filename = self ._get_output_filename (json_file_path , output_filename )
43+
44+ try :
45+ with open (filename , 'w' ) as f :
46+ f .write (content )
47+ print (f"Generated { filename } " )
48+ return filename
49+ except IOError as e :
50+ print (f"Error writing { filename } : { e } " , file = sys .stderr )
51+ sys .exit (1 )
52+
2653 def get_result_counts (self ):
2754 """Get summary statistics for results."""
2855 return {
@@ -38,6 +65,10 @@ def get_result_counts(self):
3865class AsciiDocReporter (BaseReporter ):
3966 """Generates AsciiDoc format reports."""
4067
68+ def get_filename (self ):
69+ """Get the output filename for this report type."""
70+ return 'report.adoc'
71+
4172 def generate (self ) -> str :
4273 """Generate AsciiDoc report."""
4374 content = []
@@ -220,6 +251,10 @@ def _write_output_sections(self, data, depth, is_first=True):
220251class GitHubMarkdownReporter (BaseReporter ):
221252 """Generates GitHub-flavored Markdown with emoji icons."""
222253
254+ def get_filename (self ):
255+ """Get the output filename for this report type."""
256+ return 'result-gh.md'
257+
223258 def generate (self ) -> str :
224259 """Generate GitHub Markdown report."""
225260 content = ["# Test Result" , "" ]
@@ -254,6 +289,10 @@ def _write_tree(self, data, depth):
254289class PlainMarkdownReporter (BaseReporter ):
255290 """Generates plain Markdown format."""
256291
292+ def get_filename (self ):
293+ """Get the output filename for this report type."""
294+ return 'result.md'
295+
257296 def generate (self ) -> str :
258297 """Generate plain Markdown report."""
259298 content = ["# Test Result" , "" ]
@@ -277,6 +316,54 @@ def _write_tree(self, data, depth):
277316 return content
278317
279318
319+ class PDFReporter (BaseReporter ):
320+ """Generates PDF reports via AsciiDoc conversion."""
321+
322+ def get_filename (self ):
323+ """Get the output filename for this report type."""
324+ return 'report.pdf'
325+
326+ def generate (self ) -> str :
327+ """Generate AsciiDoc content (same as AsciiDocReporter)."""
328+ return AsciiDocReporter (self .data ).generate ()
329+
330+ def write (self , json_file_path , output_filename = None ):
331+ """Generate AsciiDoc content and convert to PDF using temp file."""
332+ import tempfile
333+
334+ content = self .generate ()
335+ pdf_filename = self ._get_output_filename (json_file_path , output_filename )
336+
337+ with tempfile .NamedTemporaryFile (mode = 'w' , suffix = '.adoc' , delete = False ) as temp_adoc :
338+ temp_adoc .write (content )
339+ temp_adoc_path = temp_adoc .name
340+
341+ try :
342+ script_dir = os .path .dirname (os .path .abspath (__file__ ))
343+ cmd = ['asciidoctor-pdf' ,
344+ '--theme' , os .path .join (script_dir , 'report' , 'theme.yml' ),
345+ '-a' , f'pdf-fontsdir={ os .path .join (script_dir , "report" , "fonts" )} ' ,
346+ '-a' , f'logo=image:{ os .path .join (script_dir , "logo.png" )} [top=40%, align=right, pdfwidth=8cm]' ,
347+ '-o' , pdf_filename , temp_adoc_path ]
348+
349+ result = subprocess .run (cmd , capture_output = True , text = True )
350+ if result .returncode == 0 :
351+ print (f"Generated { pdf_filename } " )
352+ return pdf_filename
353+ else :
354+ print (f"Error generating PDF: { result .stderr } " , file = sys .stderr )
355+ sys .exit (1 )
356+
357+ except FileNotFoundError :
358+ print ("Error: asciidoctor-pdf not found. Install with: gem install asciidoctor-pdf" , file = sys .stderr )
359+ sys .exit (1 )
360+ except Exception as e :
361+ print (f"Error running asciidoctor-pdf: { e } " , file = sys .stderr )
362+ sys .exit (1 )
363+ finally :
364+ os .unlink (temp_adoc_path )
365+
366+
280367def load_json_data (json_file ):
281368 """Load and parse the JSON result file."""
282369 try :
@@ -293,45 +380,33 @@ def load_json_data(json_file):
293380def main ():
294381 """Main CLI interface."""
295382 parser = argparse .ArgumentParser (description = 'Generate test reports from JSON data' )
296- parser .add_argument ('json_file' , help = 'Path to the JSON result file' )
297- parser .add_argument ('format' , choices = ['github' , 'markdown' , 'asciidoc' , 'all' ],
383+ parser .add_argument ('format' , choices = ['github' , 'markdown' , 'asciidoc' , 'pdf' ],
298384 help = 'Report format to generate' )
299- parser .add_argument ('-o ' , '--output-dir ' , default = '.' ,
300- help = 'Output directory for generated reports (default: current directory )' )
385+ parser .add_argument ('json_file ' , nargs = '? ' , help = 'Path to the JSON result file (auto-find if omitted)' )
386+ parser . add_argument ( '-o' , '--output' , help = 'Output filename (auto-generate if omitted )' )
301387
302388 args = parser .parse_args ()
303389
390+ # Auto-find JSON file if not provided
391+ if args .json_file is None :
392+ args .json_file = os .path .expanduser ('~/.local/share/9pm/logs/last/result.json' )
393+
304394 # Load JSON data
305395 json_data = load_json_data (args .json_file )
306396
307- # Generate reports based on format
308- if args .format == 'all' :
309- formats = ['github' , 'markdown' , 'asciidoc' ]
310- else :
311- formats = [args .format ]
312-
313- for fmt in formats :
314- if fmt == 'github' :
315- reporter = GitHubMarkdownReporter (json_data )
316- content = reporter .generate ()
317- filename = os .path .join (args .output_dir , 'result-gh.md' )
318- elif fmt == 'markdown' :
319- reporter = PlainMarkdownReporter (json_data )
320- content = reporter .generate ()
321- filename = os .path .join (args .output_dir , 'result.md' )
322- elif fmt == 'asciidoc' :
323- reporter = AsciiDocReporter (json_data )
324- content = reporter .generate ()
325- filename = os .path .join (args .output_dir , 'report.adoc' )
326-
327- # Write output file
328- try :
329- with open (filename , 'w' ) as f :
330- f .write (content )
331- print (f"Generated { filename } " )
332- except IOError as e :
333- print (f"Error writing { filename } : { e } " , file = sys .stderr )
334- sys .exit (1 )
397+ # Generate report based on format
398+ try :
399+ if args .format == 'github' :
400+ GitHubMarkdownReporter (json_data ).write (args .json_file , args .output )
401+ elif args .format == 'markdown' :
402+ PlainMarkdownReporter (json_data ).write (args .json_file , args .output )
403+ elif args .format == 'asciidoc' :
404+ AsciiDocReporter (json_data ).write (args .json_file , args .output )
405+ elif args .format == 'pdf' :
406+ PDFReporter (json_data ).write (args .json_file , args .output )
407+ except Exception as e :
408+ print (f"Error generating report: { e } " , file = sys .stderr )
409+ sys .exit (1 )
335410
336411
337412if __name__ == '__main__' :
0 commit comments