From 6ea7fc0587ba1645ee4bdd4c610480e55c4a7d96 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Wed, 28 Jun 2017 15:43:15 -0400 Subject: Removed the ability for load command to load a script from a URL From what I can tell, this hasn't worked in years and there aren't any unit tests which display how it is supposed to work. --- cmd2.py | 53 ++++++++++------------------------------------------- 1 file changed, 10 insertions(+), 43 deletions(-) (limited to 'cmd2.py') diff --git a/cmd2.py b/cmd2.py index 3ae1c599..1c62d675 100755 --- a/cmd2.py +++ b/cmd2.py @@ -58,10 +58,6 @@ import six.moves as sm # itertools.zip() for Python 2 or zip() for Python 3 - produces an iterator in both cases from six.moves import zip -# Python 2 urllib2.urlopen() or Python3 urllib.request.urlopen() -# noinspection PyUnresolvedReferences -from six.moves.urllib.request import urlopen - # Python 3 compatibility hack due to no built-in file keyword in Python 3 # Due to one occurrence of isinstance(, file) checking to see if something is of file type try: @@ -587,7 +583,6 @@ class Cmd(cmd.Cmd): commentInProgress = pyparsing.Literal('/*') + pyparsing.SkipTo(pyparsing.stringEnd ^ '*/') default_to_shell = False # Attempt to run unrecognized commands as shell commands - defaultExtension = 'txt' # For ``save``, ``load``, etc. excludeFromHistory = '''run r list l history hi ed edit li eof'''.split() exclude_from_help = ['do_eof'] # Commands to exclude from the help menu @@ -599,7 +594,6 @@ class Cmd(cmd.Cmd): reserved_words = [] shortcuts = {'?': 'help', '!': 'shell', '@': 'load', '@@': '_relative_load'} terminators = [';'] - urlre = re.compile('(https?://[-\\w./]+)') # Attributes which ARE dynamically settable at runtime abbrev = True # Abbreviated commands recognized @@ -1765,43 +1759,13 @@ Edited files are run on close if the ``autorun_on_edit`` settable parameter is T self.perror('Error saving {}'.format(fname)) raise - def _read_file_or_url(self, fname): - """Open a file or URL for reading by the do_load() method. - - This method methodically proceeds in the following path until it succeeds (or fails in the end): - 1) Try to open the file - 2) Try to open the URL if it looks like one - 3) Try to expand the ~ to create an absolute path for the filename - 4) Try to add the default extension to the expanded path - 5) Raise an error - - :param fname: str - filename or URL - :return: stream or a file-like object pointing to the file or URL (or raise an exception if it couldn't open) - """ - # TODO: not working on localhost - if os.path.isfile(fname): - result = open(fname, 'r') - else: - match = self.urlre.match(fname) - if match: - result = urlopen(match.group(1)) - else: - fname = os.path.expanduser(fname) - try: - result = open(os.path.expanduser(fname), 'r') - except IOError: - result = open('%s.%s' % (os.path.expanduser(fname), - self.defaultExtension), 'r') - return result - def do__relative_load(self, arg=None): - """Runs commands in script at file or URL. + """Runs commands in script file that is encoded as either ASCII or UTF-8 text. Usage: _relative_load [file_path] optional argument: - file_path a file path or URL pointing to a script - default: value stored in `default_file_name` settable param + file_path a file path pointing to a script Script should contain one command per line, just like command would be typed in console. @@ -1817,11 +1781,11 @@ NOTE: This command is intended to only be used within text file scripts. self.do_load('%s %s' % (targetname, args)) def do_load(self, file_path=None): - """Runs commands in script at file or URL. + """Runs commands in script file that is encoded as either ASCII or UTF-8 text. Usage: load [file_path] - * file_path - a file path or URL pointing to a script (default: value stored in `default_file_name` param) + * file_path - a file path pointing to a script Script should contain one command per line, just like command would be typed in console. """ @@ -1831,17 +1795,20 @@ Script should contain one command per line, just like command would be typed in else: file_path = file_path.split(None, 1) targetname, args = file_path[0], (file_path[1:] or [''])[0].strip() + + expanded_path = os.path.expanduser(targetname) try: - target = self._read_file_or_url(targetname) + target = open(expanded_path) except IOError as e: - self.perror('Problem accessing script from %s: \n%s' % (targetname, e)) + self.perror('Problem accessing script from {}:\n{}'.format(expanded_path, e)) return + keepstate = Statekeeper(self, ('stdin', 'use_rawinput', 'prompt', 'continuation_prompt', '_current_script_dir')) self.stdin = target self.use_rawinput = False self.prompt = self.continuation_prompt = '' - self._current_script_dir = os.path.split(targetname)[0] + self._current_script_dir = os.path.dirname(expanded_path) stop = self._cmdloop() self.stdin.close() keepstate.restore() -- cgit v1.2.1 From 1931ce6013fff1804cf1658759eff80a64fb131a Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Wed, 28 Jun 2017 16:28:47 -0400 Subject: Simplified implementation of do__relative_load Also removed default argument for _relative_load and load --- cmd2.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) (limited to 'cmd2.py') diff --git a/cmd2.py b/cmd2.py index 1c62d675..9a445d98 100755 --- a/cmd2.py +++ b/cmd2.py @@ -1759,10 +1759,10 @@ Edited files are run on close if the ``autorun_on_edit`` settable parameter is T self.perror('Error saving {}'.format(fname)) raise - def do__relative_load(self, arg=None): + def do__relative_load(self, file_path): """Runs commands in script file that is encoded as either ASCII or UTF-8 text. - Usage: _relative_load [file_path] + Usage: _relative_load optional argument: file_path a file path pointing to a script @@ -1774,29 +1774,31 @@ relative to the already-running script's directory. NOTE: This command is intended to only be used within text file scripts. """ - if arg: - arg = arg.split(None, 1) - targetname, args = arg[0], (arg[1:] or [''])[0] - targetname = os.path.join(self._current_script_dir or '', targetname) - self.do_load('%s %s' % (targetname, args)) + # If arg is None or arg is an empty string this is an error + if not file_path: + self.perror('_relative_load command requires a file path:\n', traceback_war=False) + return - def do_load(self, file_path=None): + file_path = file_path.strip() + # NOTE: Relative path is an absolute path, it is just relative to the current script directory + relative_path = os.path.join(self._current_script_dir or '', file_path) + self.do_load(relative_path) + + def do_load(self, file_path): """Runs commands in script file that is encoded as either ASCII or UTF-8 text. - Usage: load [file_path] + Usage: load * file_path - a file path pointing to a script Script should contain one command per line, just like command would be typed in console. """ - # If arg is None or arg is an empty string, use the default filename + # If arg is None or arg is an empty string this is an error if not file_path: - targetname = self.default_file_name - else: - file_path = file_path.split(None, 1) - targetname, args = file_path[0], (file_path[1:] or [''])[0].strip() + self.perror('load command requires a file path:\n', traceback_war=False) + return - expanded_path = os.path.expanduser(targetname) + expanded_path = os.path.abspath(os.path.expanduser(file_path.strip())) try: target = open(expanded_path) except IOError as e: -- cgit v1.2.1 From 662b0857825b850982bf188fca60801f29f5d54f Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Wed, 28 Jun 2017 16:47:51 -0400 Subject: Updated comments to relect where default_file_name is used self.default_file_name - No longer used in load or _relative_load commands - Still used in edit and save commands Also fixed a crash that would occur if the save command was run with an empty history. --- cmd2.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'cmd2.py') diff --git a/cmd2.py b/cmd2.py index 9a445d98..73ed064b 100755 --- a/cmd2.py +++ b/cmd2.py @@ -602,7 +602,7 @@ class Cmd(cmd.Cmd): colors = (platform.system() != 'Windows') continuation_prompt = '> ' debug = False - default_file_name = 'command.txt' # For ``save``, ``load``, etc. + default_file_name = 'command.txt' # For ``edit`` when called with a history item number and ``save`` echo = False editor = os.environ.get('EDITOR') if not editor: @@ -622,14 +622,14 @@ class Cmd(cmd.Cmd): settable = stubborn_dict(''' abbrev Accept abbreviated commands autorun_on_edit Automatically run files after editing - case_insensitive upper- and lower-case both OK + case_insensitive Upper- and lower-case both OK colors Colorized output (*nix only) continuation_prompt On 2nd+ line of input debug Show full error stack on error - default_file_name for ``save``, ``load``, etc. + default_file_name For ``edit`` and ``save`` echo Echo command issued into output editor Program used by ``edit`` - feedback_to_output include nonessentials in `|`, `>` results + feedback_to_output Include nonessentials in `|`, `>` results locals_in_py Allow access to your application in py via self prompt The prompt issued to solicit input quiet Don't print nonessential feedback @@ -1748,8 +1748,13 @@ Edited files are run on close if the ``autorun_on_edit`` settable parameter is T elif args.idx: saveme = self.history[int(args.idx) - 1] else: - # Since this save command has already been added to history, need to go one more back for previous - saveme = self.history[-2] + saveme = '' + # Wrap in try to deal with case of empty history + try: + # Since this save command has already been added to history, need to go one more back for previous + saveme = self.history[-2] + except IndexError: + pass try: f = open(os.path.expanduser(fname), 'w') f.write(saveme) -- cgit v1.2.1 From 38f509964141530a246effb87c0d3ed30e6918b1 Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Wed, 28 Jun 2017 17:10:10 -0400 Subject: Fixed crash in edit if called with no argument and empty history --- cmd2.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'cmd2.py') diff --git a/cmd2.py b/cmd2.py index 73ed064b..a55fb09e 100755 --- a/cmd2.py +++ b/cmd2.py @@ -1714,7 +1714,11 @@ Edited files are run on close if the ``autorun_on_edit`` settable parameter is T filename = arg buffer = '' else: - buffer = self.history[-1] + try: + buffer = self.history[-1] + except IndexError: + self.perror('edit must be called with argument if history is empty', traceback_war=False) + return if buffer: f = open(os.path.expanduser(filename), 'w') -- cgit v1.2.1 From 9efa8ee8d0e14d494eb954cd356d17832573deee Mon Sep 17 00:00:00 2001 From: Todd Leonhardt Date: Wed, 28 Jun 2017 17:57:23 -0400 Subject: Completely removed use of self.default_file_name Load and relative load now require a file path Edit will use a temporary file by default and delete it when done Save will use a temporary file by default and inform the user what it is Also changed the default value for autorun_on_edit to False so that it can safely be used as an actual file editor. --- cmd2.py | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) (limited to 'cmd2.py') diff --git a/cmd2.py b/cmd2.py index a55fb09e..87140ad4 100755 --- a/cmd2.py +++ b/cmd2.py @@ -597,12 +597,11 @@ class Cmd(cmd.Cmd): # Attributes which ARE dynamically settable at runtime abbrev = True # Abbreviated commands recognized - autorun_on_edit = True # Should files automatically run after editing (doesn't apply to commands) + autorun_on_edit = False # Should files automatically run after editing (doesn't apply to commands) case_insensitive = True # Commands recognized regardless of case colors = (platform.system() != 'Windows') continuation_prompt = '> ' debug = False - default_file_name = 'command.txt' # For ``edit`` when called with a history item number and ``save`` echo = False editor = os.environ.get('EDITOR') if not editor: @@ -626,7 +625,6 @@ class Cmd(cmd.Cmd): colors Colorized output (*nix only) continuation_prompt On 2nd+ line of input debug Show full error stack on error - default_file_name For ``edit`` and ``save`` echo Echo command issued into output editor Program used by ``edit`` feedback_to_output Include nonessentials in `|`, `>` results @@ -929,7 +927,8 @@ class Cmd(cmd.Cmd): # TODO: Once support for Python 3.x prior to 3.5 is no longer necessary, replace with a real subprocess pipe # Redirect stdout to a temporary file - _, self._temp_filename = tempfile.mkstemp() + fd, self._temp_filename = tempfile.mkstemp() + os.close(fd) self.stdout = open(self._temp_filename, 'w') elif statement.parsed.output: if (not statement.parsed.outputTo) and (not can_clip): @@ -1706,7 +1705,7 @@ Edited commands are always run after the editor is closed. Edited files are run on close if the ``autorun_on_edit`` settable parameter is True.""" if not self.editor: raise EnvironmentError("Please use 'set editor' to specify your text editing program of choice.") - filename = self.default_file_name + filename = None if arg: try: buffer = self._last_matching(int(arg)) @@ -1720,7 +1719,13 @@ Edited files are run on close if the ``autorun_on_edit`` settable parameter is T self.perror('edit must be called with argument if history is empty', traceback_war=False) return + delete_tempfile = False if buffer: + if filename is None: + fd, filename = tempfile.mkstemp(suffix='.txt', text=True) + os.close(fd) + delete_tempfile = True + f = open(os.path.expanduser(filename), 'w') f.write(buffer or '') f.close() @@ -1730,6 +1735,9 @@ Edited files are run on close if the ``autorun_on_edit`` settable parameter is T if self.autorun_on_edit or buffer: self.do_load(filename) + if delete_tempfile: + os.remove(filename) + saveparser = (pyparsing.Optional(pyparsing.Word(pyparsing.nums) ^ '*')("idx") + pyparsing.Optional(pyparsing.Word(legalChars + '/\\'))("fname") + pyparsing.stringEnd) @@ -1740,13 +1748,20 @@ Edited files are run on close if the ``autorun_on_edit`` settable parameter is T Usage: save [N] [file_path] * N - Number of command (from history), or `*` for all commands in history (default: last command) - * file_path - location to save script of command(s) to (default: value stored in `default_file_name` param)""" + * file_path - location to save script of command(s) to (default: value stored in temporary file)""" try: args = self.saveparser.parseString(arg) except pyparsing.ParseException: self.perror('Could not understand save target %s' % arg) raise SyntaxError(self.do_save.__doc__) - fname = args.fname or self.default_file_name + + # If a filename was supplied then use that, otherwise use a temp file + if args.fname: + fname = args.fname + else: + fd, fname = tempfile.mkstemp(suffix='.txt', text=True) + os.close(fd) + if args.idx == '*': saveme = '\n\n'.join(self.history[:]) elif args.idx: -- cgit v1.2.1