@@ -4614,7 +4614,16 @@ def _has_steps(script: str) -> bool:
46144614)
46154615@click .option ("--update" , is_flag = True , help = "Re-record the recordings that have drifted" )
46164616@click .option ("--shell" , type = str , default = None , help = "Run the steps in this shell instead" )
4617- def term_check (scripts : tuple [str , ...], ignore : tuple [str , ...], update : bool , shell : str | None ):
4617+ @click .option (
4618+ "--json" , "as_json" , is_flag = True , help = "Print the results as JSON, for CI and other tools"
4619+ )
4620+ def term_check (
4621+ scripts : tuple [str , ...],
4622+ ignore : tuple [str , ...],
4623+ update : bool ,
4624+ shell : str | None ,
4625+ as_json : bool ,
4626+ ):
46184627 """Check that recordings made from steps still match what the steps produce.
46194628
46204629 Each script's steps are run again, as `record --steps` would, and the
@@ -4629,10 +4638,15 @@ def term_check(scripts: tuple[str, ...], ignore: tuple[str, ...], update: bool,
46294638 Exits with status 1 if any recording has drifted or a step failed. With
46304639 --update, recordings that have drifted are recorded again instead.
46314640
4641+ With --json, the results are printed as one JSON object instead, with
4642+ the same exit status. Progress still goes to stderr.
4643+
46324644 Examples:
46334645 great-docs termshow check docs/assets/*.termshow.yml
46344646 great-docs termshow check docs/assets/install.termshow --ignore 'v\\ d+\\ .\\ d+'
4647+ great-docs termshow check docs/assets/*.termshow.yml --json > drift.json
46354648 """
4649+ import json
46364650 import re
46374651
46384652 from ._term_player .check import CheckError , check_script
@@ -4647,54 +4661,80 @@ def term_check(scripts: tuple[str, ...], ignore: tuple[str, ...], update: bool,
46474661 f"{ pattern !r} isn't a valid regular expression ({ e } )" , param_hint = "--ignore"
46484662 ) from None
46494663
4650- drifted = failed = 0
4664+ results = []
46514665 for script in scripts :
46524666 script_path = Path (script )
46534667 if script_path .name .endswith (".termshow" ):
46544668 script_path = script_path .with_name (script_path .name + ".yml" )
46554669 click .echo (f"Checking { script_path } " , err = True )
4670+ entry = {"script" : script_path .as_posix (), "recording" : None }
46564671 try :
46574672 result = check_script (script_path , ignore = ignore , shell = shell )
4658- except CheckError as e :
4659- click .echo (f"✗ { script_path } : { e } " )
4660- failed += 1
4661- continue
4662- except StepsError as e :
4663- click .echo (f"✗ { script_path } : { e } " )
4664- if e .screen :
4665- click .echo ("\n The screen at the time:" )
4666- for line in e .screen .splitlines ():
4667- click .echo (f" │ { line } " )
4668- failed += 1
4673+ except (CheckError , StepsError ) as e :
4674+ entry |= {"status" : "failed" , "error" : str (e )}
4675+ if isinstance (e , StepsError ) and e .screen :
4676+ entry ["screen" ] = e .screen
4677+ results .append (entry )
4678+ if not as_json :
4679+ _echo_check_failure (entry )
46694680 continue
46704681
4671- screens = f"{ result .snapshots } screen{ '' if result .snapshots == 1 else 's' } "
4672- if not result .drifted :
4673- click .echo (f"✓ { result .recording_path } is up to date ({ screens } compared)" )
4674- continue
4675- drifted += 1
4676- click .echo (f"✗ { result .recording_path } has drifted:\n " )
4677- for difference in result .differences :
4678- for line in difference .lines :
4679- color = {"+" : "green" , "-" : "red" , "@" : "cyan" }.get (line [:1 ])
4680- if line .startswith (("+++" , "---" )):
4681- color = None
4682- click .secho (f" { line } " , fg = color )
4683- click .echo ()
4684- if update :
4682+ entry |= {
4683+ "recording" : result .recording_path .as_posix (),
4684+ "status" : "drifted" if result .drifted else "up_to_date" ,
4685+ "compared" : result .snapshots ,
4686+ "differences" : [{"at" : d .where , "diff" : d .lines } for d in result .differences ],
4687+ }
4688+ if result .drifted and update :
46854689 _write_termshow (result .recording , result .recording_path )
4686- click .echo (f"↻ Recorded { result .recording_path } again" )
4687-
4688- if len (scripts ) > 1 :
4690+ entry ["status" ] = "updated"
4691+ results .append (entry )
4692+ if not as_json :
4693+ _echo_check_result (entry )
4694+
4695+ counts = {
4696+ status : sum (r ["status" ] == status for r in results )
4697+ for status in ("up_to_date" , "drifted" , "updated" , "failed" )
4698+ }
4699+ if as_json :
4700+ click .echo (json .dumps ({"version" : 1 , "summary" : counts , "results" : results }, indent = 2 ))
4701+ elif len (scripts ) > 1 :
4702+ changed = counts ["drifted" ] + counts ["updated" ]
46894703 click .echo (
4690- f"\n { len ( scripts ) - drifted - failed } of { len (scripts )} up to date"
4691- + (f", { drifted } { 'updated' if update else 'drifted' } " if drifted else "" )
4692- + (f", { failed } couldn't be checked" if failed else "" )
4704+ f"\n { counts [ 'up_to_date' ] } of { len (scripts )} up to date"
4705+ + (f", { changed } { 'updated' if update else 'drifted' } " if changed else "" )
4706+ + (f", { counts [ ' failed' ] } couldn't be checked" if counts [ " failed" ] else "" )
46934707 )
4694- if failed or ( drifted and not update ) :
4708+ if counts [ " failed" ] or counts [ " drifted" ] :
46954709 raise SystemExit (1 )
46964710
46974711
4712+ def _echo_check_failure (entry : dict ) -> None :
4713+ click .echo (f"✗ { entry ['script' ]} : { entry ['error' ]} " )
4714+ if entry .get ("screen" ):
4715+ click .echo ("\n The screen at the time:" )
4716+ for line in entry ["screen" ].splitlines ():
4717+ click .echo (f" │ { line } " )
4718+
4719+
4720+ def _echo_check_result (entry : dict ) -> None :
4721+ recording = entry ["recording" ]
4722+ if entry ["status" ] == "up_to_date" :
4723+ screens = f"{ entry ['compared' ]} screen{ '' if entry ['compared' ] == 1 else 's' } "
4724+ click .echo (f"✓ { recording } is up to date ({ screens } compared)" )
4725+ return
4726+ click .echo (f"✗ { recording } has drifted:\n " )
4727+ for difference in entry ["differences" ]:
4728+ for line in difference ["diff" ]:
4729+ color = {"+" : "green" , "-" : "red" , "@" : "cyan" }.get (line [:1 ])
4730+ if line .startswith (("+++" , "---" )):
4731+ color = None
4732+ click .secho (f" { line } " , fg = color )
4733+ click .echo ()
4734+ if entry ["status" ] == "updated" :
4735+ click .echo (f"↻ Recorded { recording } again" )
4736+
4737+
46984738@click .command ("assets" )
46994739@click .argument ("dest" , type = click .Path (file_okay = False ))
47004740def term_assets (dest : str ) -> None :
0 commit comments