@@ -416,8 +416,8 @@ class Cmd(cmd.Cmd):
416416 allow_cli_args = True # Should arguments passed on the command-line be processed as commands?
417417 allow_redirection = True # Should output redirection and pipes be allowed
418418 default_to_shell = False # Attempt to run unrecognized commands as shell commands
419- excludeFromHistory = '''run ru r history histor histo hist his hi h edit edi ed e eof eo''' .split ()
420- exclude_from_help = ['do_eof' ] # Commands to exclude from the help menu
419+ excludeFromHistory = '''run ru r history histor histo hist his hi h edit edi ed e eof eo eos ''' .split ()
420+ exclude_from_help = ['do_eof' , 'do_eos' ] # Commands to exclude from the help menu
421421 reserved_words = []
422422
423423 # Attributes which ARE dynamically settable at runtime
@@ -507,8 +507,7 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, use_ipython=False
507507 self ._temp_filename = None
508508
509509 # Codes used for exit conditions
510- self ._STOP_AND_EXIT = True # distinguish end of script file from actual exit
511- self ._STOP_SCRIPT_NO_EXIT = - 999
510+ self ._STOP_AND_EXIT = True # cmd convention
512511
513512 self ._colorcodes = {'bold' : {True : '\x1b [1m' , False : '\x1b [22m' },
514513 'cyan' : {True : '\x1b [36m' , False : '\x1b [39m' },
@@ -519,8 +518,8 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, use_ipython=False
519518 'underline' : {True : '\x1b [4m' , False : '\x1b [24m' },
520519 'yellow' : {True : '\x1b [33m' , False : '\x1b [39m' }}
521520
522- # Used by load and _relative_load commands
523- self ._current_script_dir = None
521+ # Used load command to store the current script dir as a LIFO queue to support _relative_load command
522+ self ._script_dir = []
524523
525524 # ----- Methods related to presenting output to the user -----
526525
@@ -829,7 +828,6 @@ def onecmd(self, line):
829828 :return: bool - a flag indicating whether the interpretation of commands should stop
830829 """
831830 statement = self .parser_manager .parsed (line )
832- self .lastcmd = statement .parsed .raw
833831 funcname = self ._func_named (statement .parsed .command )
834832 if not funcname :
835833 return self ._default (statement )
@@ -1020,9 +1018,9 @@ def do_shortcuts(self, args):
10201018
10211019 # noinspection PyUnusedLocal
10221020 def do_eof (self , arg ):
1023- """Automatically called at end of loading a script or when <Ctrl>-D is pressed."""
1021+ """Called when <Ctrl>-D is pressed."""
10241022 # End of script should not exit app, but <Ctrl>-D should.
1025- return self ._STOP_SCRIPT_NO_EXIT
1023+ return self ._STOP_AND_EXIT
10261024
10271025 def do_quit (self , arg ):
10281026 """Exits this application."""
@@ -1591,6 +1589,14 @@ def do_save(self, arg):
15911589 except Exception as e :
15921590 self .perror ('Saving {!r} - {}' .format (fname , e ), traceback_war = False )
15931591
1592+ @property
1593+ def _current_script_dir (self ):
1594+ """Accessor to get the current script directory from the _script_dir LIFO queue."""
1595+ if self ._script_dir :
1596+ return self ._script_dir [- 1 ]
1597+ else :
1598+ return None
1599+
15941600 def do__relative_load (self , file_path ):
15951601 """Runs commands in script file that is encoded as either ASCII or UTF-8 text.
15961602
@@ -1616,6 +1622,11 @@ def do__relative_load(self, file_path):
16161622 relative_path = os .path .join (self ._current_script_dir or '' , file_path )
16171623 self .do_load (relative_path )
16181624
1625+ def do_eos (self , _ ):
1626+ """Handles cleanup when a script has finished executing."""
1627+ if self ._script_dir :
1628+ self ._script_dir .pop ()
1629+
16191630 def do_load (self , file_path ):
16201631 """Runs commands in script file that is encoded as either ASCII or UTF-8 text.
16211632
@@ -1648,22 +1659,23 @@ def do_load(self, file_path):
16481659 return
16491660
16501661 try :
1651- target = open (expanded_path )
1662+ # Specify file encoding in Python 3, but Python 2 doesn't allow that argument to open()
1663+ if six .PY3 :
1664+ # Add all commands in the script to the command queue
1665+ with open (expanded_path , encoding = 'utf-8' ) as target :
1666+ self .cmdqueue .extend (target .read ().splitlines ())
1667+ else :
1668+ # Add all commands in the script to the command queue
1669+ with open (expanded_path ) as target :
1670+ self .cmdqueue .extend (target .read ().splitlines ())
1671+
1672+ # Append in an "end of script (eos)" command to cleanup the self._script_dir list
1673+ self .cmdqueue .append ('eos' )
16521674 except IOError as e :
16531675 self .perror ('Problem accessing script from {}:\n {}' .format (expanded_path , e ))
16541676 return
16551677
1656- keepstate = Statekeeper (self , ('stdin' , 'use_rawinput' , 'prompt' ,
1657- 'continuation_prompt' , '_current_script_dir' ))
1658- self .stdin = target
1659- self .use_rawinput = False
1660- self .prompt = self .continuation_prompt = ''
1661- self ._current_script_dir = os .path .dirname (expanded_path )
1662- stop = self ._cmdloop ()
1663- self .stdin .close ()
1664- keepstate .restore ()
1665- self .lastcmd = ''
1666- return stop and (stop != self ._STOP_SCRIPT_NO_EXIT )
1678+ self ._script_dir .append (os .path .dirname (expanded_path ))
16671679
16681680 def do_run (self , arg ):
16691681 """run [arg]: re-runs an earlier command
@@ -1728,16 +1740,6 @@ class TestMyAppCase(Cmd2TestCase):
17281740 runner = unittest .TextTestRunner ()
17291741 runner .run (testcase )
17301742
1731- def _run_commands_at_invocation (self , callargs ):
1732- """Runs commands provided as arguments on the command line when the application is started.
1733-
1734- :param callargs: List[str] - list of strings where each string is a command plus its arguments
1735- :return: bool - True implies the entire application should exit
1736- """
1737- for initial_command in callargs :
1738- if self .onecmd_plus_hooks (initial_command + '\n ' ):
1739- return self ._STOP_AND_EXIT
1740-
17411743 def cmdloop (self , intro = None ):
17421744 """This is an outer wrapper around _cmdloop() which deals with extra features provided by cmd2.
17431745
@@ -1749,19 +1751,25 @@ def cmdloop(self, intro=None):
17491751
17501752 :param intro: str - if provided this overrides self.intro and serves as the intro banner printed once at start
17511753 """
1752- callargs = None
17531754 if self .allow_cli_args :
17541755 parser = optparse .OptionParser ()
17551756 parser .add_option ('-t' , '--test' , dest = 'test' ,
17561757 action = "store_true" ,
17571758 help = 'Test against transcript(s) in FILE (wildcards OK)' )
17581759 (callopts , callargs ) = parser .parse_args ()
1760+
1761+ # If transcript testing was called for, use other arguments as transcript files
17591762 if callopts .test :
17601763 self ._transcript_files = callargs
17611764
1765+ # If commands were supplied at invocation, then add them to the command queue
1766+ if callargs :
1767+ self .cmdqueue .extend (callargs )
1768+
17621769 # Always run the preloop first
17631770 self .preloop ()
17641771
1772+ # If transcript-based regression testing was requested, then do that instead of the main loop
17651773 if self ._transcript_files is not None :
17661774 self .run_transcript_tests (self ._transcript_files )
17671775 else :
@@ -1773,14 +1781,8 @@ def cmdloop(self, intro=None):
17731781 if self .intro is not None :
17741782 self .stdout .write (str (self .intro ) + "\n " )
17751783
1776- stop = False
1777- # If allowed, process any commands present as arguments on the command-line, if allowed
1778- if self .allow_cli_args :
1779- stop = self ._run_commands_at_invocation (callargs )
1780-
1781- # And then call _cmdloop() if there wasn't something in those causing us to quit
1782- if not stop :
1783- self ._cmdloop ()
1784+ # And then call _cmdloop() to enter the main loop
1785+ self ._cmdloop ()
17841786
17851787 # Run the postloop() no matter what
17861788 self .postloop ()
0 commit comments