summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTodd Leonhardt <todd.leonhardt@gmail.com>2017-06-25 16:31:59 -0400
committerGitHub <noreply@github.com>2017-06-25 16:31:59 -0400
commitf1af8b762a4b1aec6501cd2ae67dd3a0aeef2bfb (patch)
treea9c304d8b36bffc6b402bfaea54a8f5cff01a3e3
parentb73117be943403cb000efd9f97bc72586261630d (diff)
parentb44ffe7d637d513be5be916f867b29272470c9bd (diff)
downloadcmd2-git-f1af8b762a4b1aec6501cd2ae67dd3a0aeef2bfb.tar.gz
Merge pull request #141 from python-cmd2/code_coverage
Fix pastebuffer/clipboard issues on macOS and Linux
-rw-r--r--.coveragerc3
-rw-r--r--.travis.yml40
-rwxr-xr-xcmd2.py54
-rw-r--r--tests/test_cmd2.py27
-rw-r--r--tests/test_transcript.py9
5 files changed, 79 insertions, 54 deletions
diff --git a/.coveragerc b/.coveragerc
index ea915f7c..bdd0ee85 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -13,9 +13,6 @@ exclude_lines =
# Don't complain if non-runnable code isn't run:
if __name__ == .__main__.:
- # Don't complain about macOS-specific code until we have a CI platform that builds on macOS
- if sys\.platform == 'darwin':
-
# (integer): the number of digits after the decimal point to display for reported coverage percentages.
precision = 1
diff --git a/.travis.yml b/.travis.yml
index f246f8cf..2d59e8c5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,5 @@
language: python
-sudo: false # enable container-based build for fast boot times on Linux
+sudo: false # false enables container-based build for fast boot times on Linux
matrix:
include:
- os: linux
@@ -27,26 +27,26 @@ matrix:
# - os: linux
# python: pypy3
# env: TOXENV=pypy3
-# # Stock OSX Python
-# - os: osx
-# language: generic
-# env: TOXENV=py27
-# # Latest Python 3.x from Homebrew
-# - os: osx
-# language: generic
-# env:
-# - TOXENV=py36
-# - BREW_INSTALL=python3
+ # Stock OSX Python
+ - os: osx
+ language: generic
+ env: TOXENV=py27
+ # Latest Python 3.x from Homebrew
+ - os: osx
+ language: generic
+ env:
+ - TOXENV=py36
+ - BREW_INSTALL=python3
install:
- - pip install tox
-# - |
-# if [[ $TRAVIS_OS_NAME == 'osx' ]]; then
-# if [[ -n "$BREW_INSTALL" ]]; then
-# brew update
-# brew install "$BREW_INSTALL"
-# fi
-# fi
-# pip install tox
+# - pip install tox
+ - |
+ if [[ $TRAVIS_OS_NAME == 'osx' ]]; then
+ if [[ -n "$BREW_INSTALL" ]]; then
+ brew update
+ brew install "$BREW_INSTALL"
+ fi
+ fi
+ pip install tox
script:
- tox
diff --git a/cmd2.py b/cmd2.py
index 1fbcc9f3..3ae1c599 100755
--- a/cmd2.py
+++ b/cmd2.py
@@ -196,17 +196,6 @@ def remaining_args(opts_plus_args, arg_list):
return remaining
-def _attr_get_(obj, attr):
- """Returns an attribute's value, or None (no error) if undefined.
- Analogous to .get() for dictionaries. Useful when checking for
- value of options that may not have been defined on a given
- method."""
- try:
- return getattr(obj, attr)
- except AttributeError:
- return None
-
-
def _which(editor):
try:
return subprocess.Popen(['which', editor], stdout=subprocess.PIPE, stderr=subprocess.STDOUT).communicate()[0]
@@ -229,10 +218,6 @@ def strip_quotes(arg):
return arg
-optparse.Values.get = _attr_get_
-options_defined = [] # used to distinguish --options from SQL-style --comments
-
-
def options(option_list, arg_desc="arg"):
"""Used as a decorator and passed a list of optparse-style options,
alters a cmd2 method to populate its ``opts`` argument from its
@@ -250,9 +235,8 @@ def options(option_list, arg_desc="arg"):
self.fast_button = True
"""
if not isinstance(option_list, list):
+ # If passed a single option instead of a list of options, convert it to a list with one option
option_list = [option_list]
- for opt in option_list:
- options_defined.append(pyparsing.Literal(opt.get_opt_string()))
def option_setup(func):
"""Decorator function which modifies on of the do_* methods that use the @options decorator.
@@ -387,9 +371,13 @@ elif sys.platform == 'darwin':
:return: str - contents of the clipboard
"""
- pbcopyproc = subprocess.Popen('pbcopy -help', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
+ pbcopyproc = subprocess.Popen('pbpaste', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
stderr=subprocess.PIPE)
- return pbcopyproc.stdout.read()
+ stdout, stderr = pbcopyproc.communicate()
+ if six.PY3:
+ return stdout.decode()
+ else:
+ return stdout
def write_to_paste_buffer(txt):
"""Paste text to the clipboard for Mac OS X.
@@ -398,7 +386,10 @@ elif sys.platform == 'darwin':
"""
pbcopyproc = subprocess.Popen('pbcopy', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
stderr=subprocess.PIPE)
- pbcopyproc.communicate(txt.encode())
+ if six.PY3:
+ pbcopyproc.communicate(txt.encode())
+ else:
+ pbcopyproc.communicate(txt)
else:
# noinspection PyUnusedLocal
def get_paste_buffer(*args):
@@ -424,7 +415,11 @@ else:
"""
xclipproc = subprocess.Popen('xclip -o -sel clip', shell=True, stdout=subprocess.PIPE,
stdin=subprocess.PIPE)
- return xclipproc.stdout.read()
+ stdout, stderr = xclipproc.communicate()
+ if six.PY3:
+ return stdout.decode()
+ else:
+ return stdout
def write_to_paste_buffer(txt):
"""Paste text to the clipboard for Linux OSes.
@@ -432,11 +427,18 @@ else:
:param txt: str - text to paste to the clipboard
"""
xclipproc = subprocess.Popen('xclip -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
- xclipproc.stdin.write(txt.encode())
+ if six.PY3:
+ xclipproc.stdin.write(txt.encode())
+ else:
+ xclipproc.stdin.write(txt)
xclipproc.stdin.close()
+
# but we want it in both the "primary" and "mouse" clipboards
xclipproc = subprocess.Popen('xclip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
- xclipproc.stdin.write(txt.encode())
+ if six.PY3:
+ xclipproc.stdin.write(txt.encode())
+ else:
+ xclipproc.stdin.write(txt)
xclipproc.stdin.close()
else:
# noinspection PyUnusedLocal
@@ -1410,7 +1412,8 @@ class Cmd(cmd.Cmd):
elif os.path.isdir(path_completions[0]) and add_sep_after_tilde:
completions[0] = os.path.sep + completions[0]
- return completions
+ # If there are multiple completions, then sort them alphabetically
+ return sorted(completions)
# Enable tab completion of paths for relevant commands
complete_edit = path_complete
@@ -1451,7 +1454,8 @@ class Cmd(cmd.Cmd):
if len(exes) == 1 and endidx == len(line):
exes[0] += ' '
- return exes
+ # If there are multiple completions, then sort them alphabetically
+ return sorted(exes)
# noinspection PyUnusedLocal
def complete_shell(self, text, line, begidx, endidx):
diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py
index c1d406f4..4fd9ca1c 100644
--- a/tests/test_cmd2.py
+++ b/tests/test_cmd2.py
@@ -36,6 +36,21 @@ def test_base_help_history(base_app):
expected = normalize(HELP_HISTORY)
assert out == expected
+def test_base_options_help(base_app, capsys):
+ run_cmd(base_app, 'show -h')
+ out, err = capsys.readouterr()
+ expected = run_cmd(base_app, 'help show')
+ # 'show -h' is the same as 'help show', other than whitespace differences of an extra newline present in 'help show'
+ assert normalize(str(out)) == expected
+
+def test_base_invalid_option(base_app, capsys):
+ run_cmd(base_app, 'show -z')
+ out, err = capsys.readouterr()
+ show_help = run_cmd(base_app, 'help show')
+ expected = ['no such option: -z']
+ expected.extend(show_help)
+ # 'show -h' is the same as 'help show', other than whitespace differences of an extra newline present in 'help show'
+ assert normalize(str(out)) == expected
def test_base_shortcuts(base_app):
out = run_cmd(base_app, 'shortcuts')
@@ -409,8 +424,8 @@ def test_send_to_paste_buffer(base_app):
run_cmd(base_app, 'help >')
expected = normalize(BASE_HELP)
- # If an appropriate tool is installed for reading the contents of the clipboard, then do so
- if can_clip:
+ # If the tools for interacting with the clipboard/pastebuffer are available
+ if cmd2.can_clip:
# Read from the clipboard
try:
# Python2
@@ -632,4 +647,10 @@ def test_cmdloop_without_rawinput():
assert out == expected
-
+@pytest.mark.skipif(not cmd2.can_clip,
+ reason="CLI utility for interacting with PasteBuffer/ClipBoard is not available")
+def test_pastebuffer_read_and_write():
+ text_to_pb = 'This is a test ...'
+ cmd2.write_to_paste_buffer(text_to_pb)
+ text_from_pb = cmd2.get_paste_buffer()
+ assert text_from_pb == text_to_pb
diff --git a/tests/test_transcript.py b/tests/test_transcript.py
index 4a7d57a6..4b6f4c99 100644
--- a/tests/test_transcript.py
+++ b/tests/test_transcript.py
@@ -15,7 +15,7 @@ import six
# Used for sm.input: raw_input() for Python 2 or input() for Python 3
import six.moves as sm
-from cmd2 import Cmd, make_option, options, Cmd2TestCase, set_use_arg_list
+from cmd2 import Cmd, make_option, options, Cmd2TestCase, set_use_arg_list, set_posix_shlex, set_strip_quotes
from conftest import run_cmd, StdOut, normalize
@@ -28,6 +28,10 @@ class CmdLineApp(Cmd):
# Need to use this older form of invoking super class constructor to support Python 2.x and Python 3.x
Cmd.__init__(self, *args, **kwargs)
self.settable.append('maxrepeats Max number of `--repeat`s allowed')
+
+ # Configure how arguments are parsed for @options commands
+ set_posix_shlex(False)
+ set_strip_quotes(True)
set_use_arg_list(False)
opts = [make_option('-p', '--piglatin', action="store_true", help="atinLay"),
@@ -54,8 +58,7 @@ class CmdLineApp(Cmd):
class DemoApp(Cmd):
- @options([make_option('-n', '--name', action="store", help="your name"),
- ])
+ @options(make_option('-n', '--name', action="store", help="your name"))
def do_hello(self, arg, opts):
"""Says hello."""
if opts.name: