diff options
author | Todd Leonhardt <todd.leonhardt@gmail.com> | 2017-06-30 16:31:40 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-06-30 16:31:40 -0400 |
commit | 309c3eceb46a841fec43bdd72d304288e0241656 (patch) | |
tree | ff8e7f63cafa4e78723e9f8d188a23bf8269239a | |
parent | 679401b5dd314843694e92ca5c7d68e4f0f63c4d (diff) | |
parent | aa18449d189cd7ac3c1ce4f99abff7f767d1e63d (diff) | |
download | cmd2-git-309c3eceb46a841fec43bdd72d304288e0241656.tar.gz |
Merge pull request #153 from kmvanbrunt/text_file
Verifying a file to be loaded as a text script is either ASCII or UTF-8
-rwxr-xr-x | cmd2.py | 64 | ||||
-rw-r--r-- | tests/scripts/binary.bin | bin | 0 -> 51 bytes | |||
-rw-r--r-- | tests/scripts/empty.txt | 0 | ||||
-rw-r--r-- | tests/scripts/utf8.txt | 1 | ||||
-rw-r--r-- | tests/test_cmd2.py | 56 |
5 files changed, 110 insertions, 11 deletions
@@ -26,6 +26,7 @@ written to use `self.stdout.write()`, Git repository on GitHub at https://github.com/python-cmd2/cmd2 """ import cmd +import codecs import collections import datetime import glob @@ -1643,31 +1644,31 @@ Edited files are run on close if the ``autorun_on_edit`` settable parameter is T filename = None if arg: try: - buffer = self._last_matching(int(arg)) + history_item = self._last_matching(int(arg)) except ValueError: filename = arg - buffer = '' + history_item = '' else: try: - buffer = self.history[-1] + history_item = self.history[-1] except IndexError: self.perror('edit must be called with argument if history is empty', traceback_war=False) return delete_tempfile = False - if buffer: + if history_item: 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.write(history_item or '') f.close() os.system('%s %s' % (self.editor, filename)) - if self.autorun_on_edit or buffer: + if self.autorun_on_edit or history_item: self.do_load(filename) if delete_tempfile: @@ -1758,6 +1759,22 @@ Script should contain one command per line, just like command would be typed in return expanded_path = os.path.abspath(os.path.expanduser(file_path.strip())) + + # Make sure expanded_path points to a file + if not os.path.isfile(expanded_path): + self.perror('{} does not exist or is not a file\n'.format(expanded_path), traceback_war=False) + return + + # Make sure the file is not empty + if os.path.getsize(expanded_path) == 0: + self.perror('{} is empty\n'.format(expanded_path), traceback_war=False) + return + + # Make sure the file is ASCII or UTF-8 encoded text + if not self.is_text_file(expanded_path): + self.perror('{} is not an ASCII or UTF-8 encoded text file\n'.format(expanded_path), traceback_war=False) + return + try: target = open(expanded_path) except IOError as e: @@ -1788,6 +1805,40 @@ Script should contain one command per line, just like command would be typed in if runme: return self.onecmd_plus_hooks(runme) + @staticmethod + def is_text_file(file_path): + """ + Returns if a file contains only ASCII or UTF-8 encoded text + :param file_path: path to the file being checked + """ + expanded_path = os.path.abspath(os.path.expanduser(file_path.strip())) + valid_text_file = False + + # Check if the file is ASCII + try: + with codecs.open(expanded_path, encoding='ascii', errors='strict') as f: + # Make sure the file has at least one line of text + # noinspection PyUnusedLocal + if sum(1 for line in f) > 0: + valid_text_file = True + except IOError: + pass + except UnicodeDecodeError: + # The file is not ASCII. Check if it is UTF-8. + try: + with codecs.open(expanded_path, encoding='utf-8', errors='strict') as f: + # Make sure the file has at least one line of text + # noinspection PyUnusedLocal + if sum(1 for line in f) > 0: + valid_text_file = True + except IOError: + pass + except UnicodeDecodeError: + # Not UTF-8 + pass + + return valid_text_file + def run_transcript_tests(self, callargs): """Runs transcript tests for provided file(s). @@ -2357,5 +2408,4 @@ class CmdResult(namedtuple_with_two_defaults('CmdResult', ['out', 'err', 'war']) if __name__ == '__main__': # If run as the main application, simply start a bare-bones cmd2 application with only built-in functionality. app = Cmd() - app.debug = True app.cmdloop() diff --git a/tests/scripts/binary.bin b/tests/scripts/binary.bin Binary files differnew file mode 100644 index 00000000..c18394ef --- /dev/null +++ b/tests/scripts/binary.bin diff --git a/tests/scripts/empty.txt b/tests/scripts/empty.txt new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/tests/scripts/empty.txt diff --git a/tests/scripts/utf8.txt b/tests/scripts/utf8.txt new file mode 100644 index 00000000..7cd59ba3 --- /dev/null +++ b/tests/scripts/utf8.txt @@ -0,0 +1 @@ +!echo γνωρίζω diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 91e0d08a..db7f8cf7 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -293,6 +293,54 @@ def test_base_load_with_empty_args(base_app, capsys): assert normalize(str(err)) == expected +def test_base_load_with_nonexistent_file(base_app, capsys): + # The way the load command works, we can't directly capture its stdout or stderr + run_cmd(base_app, 'load does_not_exist.txt') + out, err = capsys.readouterr() + + # The load command requires a path to an existing file + assert str(err).startswith("ERROR") + assert "does not exist or is not a file" in str(err) + + +def test_base_load_with_empty_file(base_app, capsys, request): + test_dir = os.path.dirname(request.module.__file__) + filename = os.path.join(test_dir, 'scripts', 'empty.txt') + + # The way the load command works, we can't directly capture its stdout or stderr + run_cmd(base_app, 'load {}'.format(filename)) + out, err = capsys.readouterr() + + # The load command requires non-empty scripts files + assert str(err).startswith("ERROR") + assert "is empty" in str(err) + + +def test_base_load_with_binary_file(base_app, capsys, request): + test_dir = os.path.dirname(request.module.__file__) + filename = os.path.join(test_dir, 'scripts', 'binary.bin') + + # The way the load command works, we can't directly capture its stdout or stderr + run_cmd(base_app, 'load {}'.format(filename)) + out, err = capsys.readouterr() + + # The load command requires non-empty scripts files + assert str(err).startswith("ERROR") + assert "is not an ASCII or UTF-8 encoded text file" in str(err) + + +def test_base_load_with_utf8_file(base_app, capsys, request): + test_dir = os.path.dirname(request.module.__file__) + filename = os.path.join(test_dir, 'scripts', 'utf8.txt') + + # The way the load command works, we can't directly capture its stdout or stderr + run_cmd(base_app, 'load {}'.format(filename)) + out, err = capsys.readouterr() + + # TODO Make this test better once shell command is fixed to used cmd2's stdout + assert str(err) == '' + + def test_base_relative_load(base_app, request): test_dir = os.path.dirname(request.module.__file__) filename = os.path.join(test_dir, 'script.txt') @@ -458,10 +506,10 @@ now: True def test_base_debug(base_app, capsys): - # Try to load a non-existent file with debug set to False by default - run_cmd(base_app, 'load does_not_exist.txt') + # Try to set a non-existent parameter with debug set to False by default + run_cmd(base_app, 'set does_not_exist 5') out, err = capsys.readouterr() - assert err.startswith('ERROR') + assert err.startswith('EXCEPTION') # Set debug true out = run_cmd(base_app, 'set debug True') @@ -472,7 +520,7 @@ now: True assert out == expected # Verify that we now see the exception traceback - run_cmd(base_app, 'load does_not_exist.txt') + run_cmd(base_app, 'set does_not_exist 5') out, err = capsys.readouterr() assert str(err).startswith('Traceback (most recent call last):') |