@@ -3350,18 +3350,22 @@ def do_history(self, args: argparse.Namespace) -> None:
33503350 for hi in history :
33513351 self .poutput (hi .pr (script = args .script , expanded = args .expanded , verbose = args .verbose ))
33523352
3353- def _generate_transcript (self , history : List [HistoryItem ], transcript_file : str ) -> None :
3353+ def _generate_transcript (self , history : List [Union [ HistoryItem , str ] ], transcript_file : str ) -> None :
33543354 """Generate a transcript file from a given history of commands."""
3355- # Save the current echo state, and turn it off. We inject commands into the
3356- # output using a different mechanism
33573355 import io
3356+ # Validate the transcript file path to make sure directory exists and write access is available
3357+ transcript_path = os .path .abspath (os .path .expanduser (transcript_file ))
3358+ transcript_dir = os .path .dirname (transcript_path )
3359+ if not os .path .isdir (transcript_dir ) or not os .access (transcript_dir , os .W_OK ):
3360+ self .perror ("{!r} is not a directory or you don't have write access" .format (transcript_dir ),
3361+ traceback_war = False )
3362+ return
33583363
3364+ # Disable echo while we manually redirect stdout to a StringIO buffer
33593365 saved_echo = self .echo
3366+ saved_stdout = self .stdout
33603367 self .echo = False
33613368
3362- # Redirect stdout to the transcript file
3363- saved_self_stdout = self .stdout
3364-
33653369 # The problem with supporting regular expressions in transcripts
33663370 # is that they shouldn't be processed in the command, just the output.
33673371 # In addition, when we generate a transcript, any slashes in the output
@@ -3397,10 +3401,9 @@ def _generate_transcript(self, history: List[HistoryItem], transcript_file: str)
33973401 # and add the regex-escaped output to the transcript
33983402 transcript += output .replace ('/' , r'\/' )
33993403
3400- # Restore stdout to its original state
3401- self .stdout = saved_self_stdout
3402- # Set echo back to its original state
3404+ # Restore altered attributes to their original state
34033405 self .echo = saved_echo
3406+ self .stdout = saved_stdout
34043407
34053408 # finally, we can write the transcript out to the file
34063409 try :
@@ -3456,9 +3459,20 @@ def do_eos(self, _: argparse.Namespace) -> None:
34563459 load_description = ("Run commands in script file that is encoded as either ASCII or UTF-8 text\n "
34573460 "\n "
34583461 "Script should contain one command per line, just like the command would be\n "
3459- "typed in the console." )
3462+ "typed in the console.\n "
3463+ "\n "
3464+ "It loads commands from a script file into a queue and then the normal cmd2\n "
3465+ "REPL resumes control and executes the commands in the queue in FIFO order.\n "
3466+ "If you attempt to redirect/pipe a load command, it will capture the output\n "
3467+ "of the load command itself, not what it adds to the queue.\n "
3468+ "\n "
3469+ "If the -r/--record_transcript flag is used, this command instead records\n "
3470+ "the output of the script commands to a transcript for testing purposes.\n "
3471+ )
34603472
34613473 load_parser = ACArgumentParser (description = load_description )
3474+ setattr (load_parser .add_argument ('-t' , '--transcript' , help = 'record the output of the script as a transcript file' ),
3475+ ACTION_ARG_CHOICES , ('path_complete' ,))
34623476 setattr (load_parser .add_argument ('script_path' , help = "path to the script file" ),
34633477 ACTION_ARG_CHOICES , ('path_complete' ,))
34643478
@@ -3492,11 +3506,16 @@ def do_load(self, args: argparse.Namespace) -> None:
34923506 # command queue. Add an "end of script (eos)" command to cleanup the
34933507 # self._script_dir list when done.
34943508 with open (expanded_path , encoding = 'utf-8' ) as target :
3495- self . cmdqueue = target .read ().splitlines () + [ 'eos' ] + self . cmdqueue
3509+ script_commands = target .read ().splitlines ()
34963510 except OSError as ex : # pragma: no cover
34973511 self .perror ("Problem accessing script from '{}': {}" .format (expanded_path , ex ))
34983512 return
34993513
3514+ if args .transcript :
3515+ self ._generate_transcript (script_commands , os .path .expanduser (args .transcript ))
3516+ return
3517+
3518+ self .cmdqueue = script_commands + ['eos' ] + self .cmdqueue
35003519 self ._script_dir .append (os .path .dirname (expanded_path ))
35013520
35023521 relative_load_description = load_description
@@ -3812,7 +3831,7 @@ def cmdloop(self, intro: Optional[str] = None) -> None:
38123831
38133832 # If transcript-based regression testing was requested, then do that instead of the main loop
38143833 if self ._transcript_files is not None :
3815- self .run_transcript_tests (self ._transcript_files )
3834+ self .run_transcript_tests ([ os . path . expanduser ( tf ) for tf in self ._transcript_files ] )
38163835 else :
38173836 # If an intro was supplied in the method call, allow it to override the default
38183837 if intro is not None :
0 commit comments