summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTodd Leonhardt <todd.leonhardt@gmail.com>2017-06-30 16:31:40 -0400
committerGitHub <noreply@github.com>2017-06-30 16:31:40 -0400
commit309c3eceb46a841fec43bdd72d304288e0241656 (patch)
treeff8e7f63cafa4e78723e9f8d188a23bf8269239a
parent679401b5dd314843694e92ca5c7d68e4f0f63c4d (diff)
parentaa18449d189cd7ac3c1ce4f99abff7f767d1e63d (diff)
downloadcmd2-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-xcmd2.py64
-rw-r--r--tests/scripts/binary.binbin0 -> 51 bytes
-rw-r--r--tests/scripts/empty.txt0
-rw-r--r--tests/scripts/utf8.txt1
-rw-r--r--tests/test_cmd2.py56
5 files changed, 110 insertions, 11 deletions
diff --git a/cmd2.py b/cmd2.py
index f54a8555..0f3a3b77 100755
--- a/cmd2.py
+++ b/cmd2.py
@@ -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
new file mode 100644
index 00000000..c18394ef
--- /dev/null
+++ b/tests/scripts/binary.bin
Binary files differ
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):')