Skip to content

Commit 452c396

Browse files
committed
Merge remote-tracking branch 'origin/master' into bash_completion
Updated argcomplete_bridge to use new constants/utils.
2 parents ae86103 + 1306eeb commit 452c396

File tree

6 files changed

+72
-62
lines changed

6 files changed

+72
-62
lines changed

cmd2/__init__.py

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,4 @@
11
#
22
# -*- coding: utf-8 -*-
3-
#
4-
# from .cmd2 import __version__, Cmd, set_posix_shlex, set_strip_quotes, AddSubmenu, CmdResult, categorize
5-
# from .cmd2 import with_argument_list, with_argparser, with_argparser_and_unknown_args, with_category
6-
7-
# Used for tab completion and word breaks. Do not change.
8-
QUOTES = ['"', "'"]
9-
REDIRECTION_CHARS = ['|', '<', '>']
10-
11-
12-
def strip_quotes(arg: str) -> str:
13-
""" Strip outer quotes from a string.
143

15-
Applies to both single and double quotes.
164

17-
:param arg: string to strip outer quotes from
18-
:return: same string with potentially outer quotes stripped
19-
"""
20-
if len(arg) > 1 and arg[0] == arg[-1] and arg[0] in QUOTES:
21-
arg = arg[1:-1]
22-
return arg

cmd2/argcomplete_bridge.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
import shlex
1919
import sys
2020

21-
from . import strip_quotes, QUOTES
21+
from . import constants
22+
from . import utils
2223

2324

2425
def tokens_for_completion(line, endidx):
@@ -42,7 +43,7 @@ def tokens_for_completion(line, endidx):
4243
Both items are None
4344
"""
4445
unclosed_quote = ''
45-
quotes_to_try = copy.copy(QUOTES)
46+
quotes_to_try = copy.copy(constants.QUOTES)
4647

4748
tmp_line = line[:endidx]
4849
tmp_endidx = endidx
@@ -86,7 +87,7 @@ def tokens_for_completion(line, endidx):
8687
raw_tokens = initial_tokens
8788

8889
# Save the unquoted tokens
89-
tokens = [strip_quotes(cur_token) for cur_token in raw_tokens]
90+
tokens = [utils.strip_quotes(cur_token) for cur_token in raw_tokens]
9091

9192
# If the token being completed had an unclosed quote, we need
9293
# to remove the closing quote that was added in order for it

cmd2/cmd2.py

Lines changed: 23 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@
4949
import pyparsing
5050
import pyperclip
5151

52+
from . import constants
53+
from . import utils
54+
5255
# Set up readline
5356
from .rl_utils import rl_force_redisplay, readline, rl_type, RlType
5457
from .argparse_completer import AutoCompleter, ACArgumentParser
@@ -134,9 +137,6 @@ def __subclasshook__(cls, C):
134137
# Strip outer quotes for convenience if POSIX_SHLEX = False
135138
STRIP_QUOTES_FOR_NON_POSIX = True
136139

137-
# Used for tab completion and word breaks. Do not change.
138-
from . import strip_quotes, QUOTES, REDIRECTION_CHARS
139-
140140
# optional attribute, when tagged on a function, allows cmd2 to categorize commands
141141
HELP_CATEGORY = 'help_category'
142142
HELP_SUMMARY = 'help_summary'
@@ -196,7 +196,7 @@ def parse_quoted_string(cmdline: str) -> List[str]:
196196
if not POSIX_SHLEX and STRIP_QUOTES_FOR_NON_POSIX:
197197
temp_arglist = []
198198
for arg in lexed_arglist:
199-
temp_arglist.append(strip_quotes(arg))
199+
temp_arglist.append(utils.strip_quotes(arg))
200200
lexed_arglist = temp_arglist
201201
return lexed_arglist
202202

@@ -362,7 +362,7 @@ def replace_with_file_contents(fname: str) -> str:
362362
"""
363363
try:
364364
# Any outer quotes are not part of the filename
365-
unquoted_file = strip_quotes(fname[0])
365+
unquoted_file = utils.strip_quotes(fname[0])
366366
with open(os.path.expanduser(unquoted_file)) as source_file:
367367
result = source_file.read()
368368
except IOError:
@@ -382,19 +382,6 @@ class EmptyStatement(Exception):
382382
pass
383383

384384

385-
# Regular expression to match ANSI escape codes
386-
ANSI_ESCAPE_RE = re.compile(r'\x1b[^m]*m')
387-
388-
389-
def strip_ansi(text: str) -> str:
390-
"""Strip ANSI escape codes from a string.
391-
392-
:param text: string which may contain ANSI escape codes
393-
:return: the same string with any ANSI escape codes removed
394-
"""
395-
return ANSI_ESCAPE_RE.sub('', text)
396-
397-
398385
def _pop_readline_history(clear_history: bool=True) -> List[str]:
399386
"""Returns a copy of readline's history and optionally clears it (default)"""
400387
# noinspection PyArgumentList
@@ -839,7 +826,7 @@ def visible_prompt(self):
839826
840827
:return: str - prompt stripped of any ANSI escape codes
841828
"""
842-
return strip_ansi(self.prompt)
829+
return utils.strip_ansi(self.prompt)
843830

844831
def _finalize_app_parameters(self):
845832
self.commentGrammars.ignore(pyparsing.quotedString).setParseAction(lambda x: '')
@@ -1005,7 +992,7 @@ def tokens_for_completion(self, line, begidx, endidx):
1005992
Both items are None
1006993
"""
1007994
unclosed_quote = ''
1008-
quotes_to_try = copy.copy(QUOTES)
995+
quotes_to_try = copy.copy(constants.QUOTES)
1009996

1010997
tmp_line = line[:endidx]
1011998
tmp_endidx = endidx
@@ -1048,7 +1035,7 @@ def tokens_for_completion(self, line, begidx, endidx):
10481035
for cur_initial_token in initial_tokens:
10491036

10501037
# Save tokens up to 1 character in length or quoted tokens. No need to parse these.
1051-
if len(cur_initial_token) <= 1 or cur_initial_token[0] in QUOTES:
1038+
if len(cur_initial_token) <= 1 or cur_initial_token[0] in constants.QUOTES:
10521039
raw_tokens.append(cur_initial_token)
10531040
continue
10541041

@@ -1060,10 +1047,10 @@ def tokens_for_completion(self, line, begidx, endidx):
10601047
cur_raw_token = ''
10611048

10621049
while True:
1063-
if cur_char not in REDIRECTION_CHARS:
1050+
if cur_char not in constants.REDIRECTION_CHARS:
10641051

10651052
# Keep appending to cur_raw_token until we hit a redirect char
1066-
while cur_char not in REDIRECTION_CHARS:
1053+
while cur_char not in constants.REDIRECTION_CHARS:
10671054
cur_raw_token += cur_char
10681055
cur_index += 1
10691056
if cur_index < len(cur_initial_token):
@@ -1094,7 +1081,7 @@ def tokens_for_completion(self, line, begidx, endidx):
10941081
raw_tokens = initial_tokens
10951082

10961083
# Save the unquoted tokens
1097-
tokens = [strip_quotes(cur_token) for cur_token in raw_tokens]
1084+
tokens = [utils.strip_quotes(cur_token) for cur_token in raw_tokens]
10981085

10991086
# If the token being completed had an unclosed quote, we need
11001087
# to remove the closing quote that was added in order for it
@@ -1469,7 +1456,7 @@ def _redirect_complete(self, text, line, begidx, endidx, compfunc):
14691456
if len(raw_tokens) > 1:
14701457

14711458
# Build a list of all redirection tokens
1472-
all_redirects = REDIRECTION_CHARS + ['>>']
1459+
all_redirects = constants.REDIRECTION_CHARS + ['>>']
14731460

14741461
# Check if there are redirection strings prior to the token being completed
14751462
seen_pipe = False
@@ -1679,7 +1666,7 @@ def complete(self, text, state):
16791666
raw_completion_token = raw_tokens[-1]
16801667

16811668
# Check if the token being completed has an opening quote
1682-
if raw_completion_token and raw_completion_token[0] in QUOTES:
1669+
if raw_completion_token and raw_completion_token[0] in constants.QUOTES:
16831670

16841671
# Since the token is still being completed, we know the opening quote is unclosed
16851672
unclosed_quote = raw_completion_token[0]
@@ -2375,11 +2362,11 @@ def _cmdloop(self):
23752362
readline.set_completer(self.complete)
23762363

23772364
# Break words on whitespace and quotes when tab completing
2378-
completer_delims = " \t\n" + ''.join(QUOTES)
2365+
completer_delims = " \t\n" + ''.join(constants.QUOTES)
23792366

23802367
if self.allow_redirection:
23812368
# If redirection is allowed, then break words on those characters too
2382-
completer_delims += ''.join(REDIRECTION_CHARS)
2369+
completer_delims += ''.join(constants.REDIRECTION_CHARS)
23832370

23842371
readline.set_completer_delims(completer_delims)
23852372

@@ -2824,13 +2811,13 @@ def do_shell(self, command):
28242811
# Check if the token is quoted. Since shlex.split() passed, there isn't
28252812
# an unclosed quote, so we only need to check the first character.
28262813
first_char = tokens[index][0]
2827-
if first_char in QUOTES:
2828-
tokens[index] = strip_quotes(tokens[index])
2814+
if first_char in constants.QUOTES:
2815+
tokens[index] = utils.strip_quotes(tokens[index])
28292816

28302817
tokens[index] = os.path.expanduser(tokens[index])
28312818

28322819
# Restore the quotes
2833-
if first_char in QUOTES:
2820+
if first_char in constants.QUOTES:
28342821
tokens[index] = first_char + tokens[index] + first_char
28352822

28362823
expanded_command = ' '.join(tokens)
@@ -3338,7 +3325,7 @@ def _build_main_parser(self, redirector, terminators, multilineCommands, legalCh
33383325
ignore=do_not_parse)('pipeTo')) + \
33393326
pyparsing.Optional(output_destination_parser +
33403327
pyparsing.SkipTo(string_end, ignore=do_not_parse).
3341-
setParseAction(lambda x: strip_quotes(x[0].strip()))('outputTo'))
3328+
setParseAction(lambda x: utils.strip_quotes(x[0].strip()))('outputTo'))
33423329

33433330
multilineCommand.setParseAction(lambda x: x[0])
33443331
oneline_command.setParseAction(lambda x: x[0])
@@ -3697,13 +3684,13 @@ def runTest(self): # was testall
36973684
def _test_transcript(self, fname, transcript):
36983685
line_num = 0
36993686
finished = False
3700-
line = strip_ansi(next(transcript))
3687+
line = utils.strip_ansi(next(transcript))
37013688
line_num += 1
37023689
while not finished:
37033690
# Scroll forward to where actual commands begin
37043691
while not line.startswith(self.cmdapp.visible_prompt):
37053692
try:
3706-
line = strip_ansi(next(transcript))
3693+
line = utils.strip_ansi(next(transcript))
37073694
except StopIteration:
37083695
finished = True
37093696
break
@@ -3727,13 +3714,13 @@ def _test_transcript(self, fname, transcript):
37273714
self.cmdapp.onecmd_plus_hooks(command)
37283715
result = self.cmdapp.stdout.read()
37293716
# Read the expected result from transcript
3730-
if strip_ansi(line).startswith(self.cmdapp.visible_prompt):
3717+
if utils.strip_ansi(line).startswith(self.cmdapp.visible_prompt):
37313718
message = '\nFile {}, line {}\nCommand was:\n{}\nExpected: (nothing)\nGot:\n{}\n'.format(
37323719
fname, line_num, command, result)
37333720
self.assert_(not (result.strip()), message)
37343721
continue
37353722
expected = []
3736-
while not strip_ansi(line).startswith(self.cmdapp.visible_prompt):
3723+
while not utils.strip_ansi(line).startswith(self.cmdapp.visible_prompt):
37373724
expected.append(line)
37383725
try:
37393726
line = next(transcript)

cmd2/constants.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#
2+
# coding=utf-8
3+
"""Constants and definitions"""
4+
5+
import re
6+
7+
# Used for command parsing, tab completion and word breaks. Do not change.
8+
QUOTES = ['"', "'"]
9+
REDIRECTION_CHARS = ['|', '<', '>']
10+
11+
# Regular expression to match ANSI escape codes
12+
ANSI_ESCAPE_RE = re.compile(r'\x1b[^m]*m')

cmd2/utils.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#
2+
# coding=utf-8
3+
"""Shared utility functions"""
4+
5+
from . import constants
6+
7+
8+
def strip_ansi(text: str) -> str:
9+
"""Strip ANSI escape codes from a string.
10+
11+
:param text: string which may contain ANSI escape codes
12+
:return: the same string with any ANSI escape codes removed
13+
"""
14+
return constants.ANSI_ESCAPE_RE.sub('', text)
15+
16+
17+
def strip_quotes(arg: str) -> str:
18+
""" Strip outer quotes from a string.
19+
20+
Applies to both single and double quotes.
21+
22+
:param arg: string to strip outer quotes from
23+
:return: same string with potentially outer quotes stripped
24+
"""
25+
if len(arg) > 1 and arg[0] == arg[-1] and arg[0] in constants.QUOTES:
26+
arg = arg[1:-1]
27+
return arg

tests/test_completion.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -322,10 +322,11 @@ def test_path_completion_doesnt_match_wildcards(cmd2_app, request):
322322
# Currently path completion doesn't accept wildcards, so will always return empty results
323323
assert cmd2_app.path_complete(text, line, begidx, endidx) == []
324324

325-
def test_path_completion_expand_user_dir(cmd2_app):
326-
# Get the current user. We can't use getpass.getuser() since
327-
# that doesn't work when running these tests on Windows in AppVeyor.
328-
user = os.path.basename(os.path.expanduser('~'))
325+
@pytest.mark.skipif(sys.platform == 'win32', reason="getpass.getuser() does not work on Windows in AppVeyor because "
326+
"no user name environment variables are set")
327+
def test_path_completion_complete_user(cmd2_app):
328+
import getpass
329+
user = getpass.getuser()
329330

330331
text = '~{}'.format(user)
331332
line = 'shell fake {}'.format(text)
@@ -336,7 +337,7 @@ def test_path_completion_expand_user_dir(cmd2_app):
336337
expected = text + os.path.sep
337338
assert expected in completions
338339

339-
def test_path_completion_user_expansion(cmd2_app):
340+
def test_path_completion_user_path_expansion(cmd2_app):
340341
# Run path with a tilde and a slash
341342
if sys.platform.startswith('win'):
342343
cmd = 'dir'

0 commit comments

Comments
 (0)