Skip to content

Commit b61728e

Browse files
committed
Added Statement object to argparse Namespace passed to wrapped functions
1 parent 8e92f9d commit b61728e

File tree

2 files changed

+76
-49
lines changed

2 files changed

+76
-49
lines changed

cmd2/cmd2.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
from .argparse_completer import AutoCompleter, ACArgumentParser, ACTION_ARG_CHOICES
5050
from .clipboard import can_clip, get_paste_buffer, write_to_paste_buffer
5151
from .history import History, HistoryItem
52-
from .parsing import StatementParser, Statement, Macro, MacroArg, shlex_split, get_command_arg_list
52+
from .parsing import StatementParser, Statement, Macro, MacroArg, shlex_split
5353

5454
# Set up readline
5555
from .rl_utils import rl_type, RlType, rl_get_point, rl_set_prompt, vt100_support, rl_make_safe_prompt
@@ -174,9 +174,13 @@ def with_argument_list(*args: List[Callable], preserve_quotes: bool = False) ->
174174
def arg_decorator(func: Callable):
175175
@functools.wraps(func)
176176
def cmd_wrapper(cmd2_instance, statement: Union[Statement, str]):
177-
parsed_arglist = get_command_arg_list(statement, preserve_quotes)
177+
_, parsed_arglist = cmd2_instance.statement_parser.get_command_arg_list(command_name,
178+
statement,
179+
preserve_quotes)
180+
178181
return func(cmd2_instance, parsed_arglist)
179182

183+
command_name = func.__name__[len(COMMAND_FUNC_PREFIX):]
180184
cmd_wrapper.__doc__ = func.__doc__
181185
return cmd_wrapper
182186

@@ -193,26 +197,33 @@ def with_argparser_and_unknown_args(argparser: argparse.ArgumentParser, preserve
193197
194198
:param argparser: unique instance of ArgumentParser
195199
:param preserve_quotes: if True, then arguments passed to argparse maintain their quotes
196-
:return: function that gets passed argparse-parsed args and a list of unknown argument strings
200+
:return: function that gets passed argparse-parsed args in a Namespace and a list of unknown argument strings
201+
A member called __statement__ is added to the Namespace to provide command functions access to the
202+
Statement object. This can be useful when knowledge of the command line is needed.
203+
197204
"""
198205
import functools
199206

200207
# noinspection PyProtectedMember
201208
def arg_decorator(func: Callable):
202209
@functools.wraps(func)
203210
def cmd_wrapper(cmd2_instance, statement: Union[Statement, str]):
204-
parsed_arglist = get_command_arg_list(statement, preserve_quotes)
211+
statement, parsed_arglist = cmd2_instance.statement_parser.get_command_arg_list(command_name,
212+
statement,
213+
preserve_quotes)
205214

206215
try:
207216
args, unknown = argparser.parse_known_args(parsed_arglist)
208217
except SystemExit:
209218
return
210219
else:
220+
setattr(args, '__statement__', statement)
211221
return func(cmd2_instance, args, unknown)
212222

213223
# argparser defaults the program name to sys.argv[0]
214224
# we want it to be the name of our command
215-
argparser.prog = func.__name__[len(COMMAND_FUNC_PREFIX):]
225+
command_name = func.__name__[len(COMMAND_FUNC_PREFIX):]
226+
argparser.prog = command_name
216227

217228
# If the description has not been set, then use the method docstring if one exists
218229
if argparser.description is None and func.__doc__:
@@ -236,27 +247,31 @@ def with_argparser(argparser: argparse.ArgumentParser,
236247
237248
:param argparser: unique instance of ArgumentParser
238249
:param preserve_quotes: if True, then arguments passed to argparse maintain their quotes
239-
:return: function that gets passed the argparse-parsed args
250+
:return: function that gets passed the argparse-parsed args in a Namespace
251+
A member called __statement__ is added to the Namespace to provide command functions access to the
252+
Statement object. This can be useful when knowledge of the command line is needed.
240253
"""
241254
import functools
242255

243256
# noinspection PyProtectedMember
244257
def arg_decorator(func: Callable):
245258
@functools.wraps(func)
246259
def cmd_wrapper(cmd2_instance, statement: Union[Statement, str]):
247-
248-
parsed_arglist = get_command_arg_list(statement, preserve_quotes)
249-
260+
statement, parsed_arglist = cmd2_instance.statement_parser.get_command_arg_list(command_name,
261+
statement,
262+
preserve_quotes)
250263
try:
251264
args = argparser.parse_args(parsed_arglist)
252265
except SystemExit:
253266
return
254267
else:
268+
setattr(args, '__statement__', statement)
255269
return func(cmd2_instance, args)
256270

257271
# argparser defaults the program name to sys.argv[0]
258272
# we want it to be the name of our command
259-
argparser.prog = func.__name__[len(COMMAND_FUNC_PREFIX):]
273+
command_name = func.__name__[len(COMMAND_FUNC_PREFIX):]
274+
argparser.prog = command_name
260275

261276
# If the description has not been set, then use the method docstring if one exists
262277
if argparser.description is None and func.__doc__:

cmd2/parsing.py

Lines changed: 51 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -236,34 +236,6 @@ def argv(self) -> List[str]:
236236
return rtn
237237

238238

239-
def get_command_arg_list(to_parse: Union[Statement, str], preserve_quotes: bool) -> List[str]:
240-
"""
241-
Called by the argument_list and argparse wrappers to retrieve just the arguments being
242-
passed to their do_* methods as a list.
243-
244-
:param to_parse: what is being passed to the do_* method. It can be one of two types:
245-
1. An already parsed Statement
246-
2. An argument string in cases where a do_* method is explicitly called
247-
e.g.: Calling do_help('alias create') would cause to_parse to be 'alias create'
248-
249-
:param preserve_quotes: if True, then quotes will not be stripped from the arguments
250-
:return: the arguments in a list
251-
"""
252-
if isinstance(to_parse, Statement):
253-
# In the case of a Statement, we already have what we need
254-
if preserve_quotes:
255-
return to_parse.arg_list
256-
else:
257-
return to_parse.argv[1:]
258-
else:
259-
# We have the arguments in a string. Use shlex to split it.
260-
parsed_arglist = shlex_split(to_parse)
261-
if not preserve_quotes:
262-
parsed_arglist = [utils.strip_quotes(arg) for arg in parsed_arglist]
263-
264-
return parsed_arglist
265-
266-
267239
class StatementParser:
268240
"""Parse raw text into command components.
269241
@@ -371,16 +343,21 @@ def is_valid_command(self, word: str) -> Tuple[bool, str]:
371343
errmsg = ''
372344
return valid, errmsg
373345

374-
def tokenize(self, line: str) -> List[str]:
375-
"""Lex a string into a list of tokens.
376-
377-
shortcuts and aliases are expanded and comments are removed
378-
379-
Raises ValueError if there are unclosed quotation marks.
346+
def tokenize(self, line: str, expand: bool = True) -> List[str]:
347+
"""
348+
Lex a string into a list of tokens. Shortcuts and aliases are expanded and comments are removed
349+
350+
:param line: the command line being lexed
351+
:param expand: if True, then aliases and shortcuts will be expanded
352+
set this to False if the first token does not need to be expanded
353+
because the command name is already known (Defaults to True)
354+
:return: A list of tokens
355+
:raises ValueError if there are unclosed quotation marks.
380356
"""
381357

382358
# expand shortcuts and aliases
383-
line = self._expand(line)
359+
if expand:
360+
line = self._expand(line)
384361

385362
# check if this line is a comment
386363
if line.strip().startswith(constants.COMMENT_CHAR):
@@ -393,12 +370,18 @@ def tokenize(self, line: str) -> List[str]:
393370
tokens = self._split_on_punctuation(tokens)
394371
return tokens
395372

396-
def parse(self, line: str) -> Statement:
397-
"""Tokenize the input and parse it into a Statement object, stripping
373+
def parse(self, line: str, expand: bool = True) -> Statement:
374+
"""
375+
Tokenize the input and parse it into a Statement object, stripping
398376
comments, expanding aliases and shortcuts, and extracting output
399377
redirection directives.
400378
401-
Raises ValueError if there are unclosed quotation marks.
379+
:param line: the command line being parsed
380+
:param expand: if True, then aliases and shortcuts will be expanded
381+
set this to False if the first token does not need to be expanded
382+
because the command name is already known (Defaults to True)
383+
:return: A parsed Statement
384+
:raises ValueError if there are unclosed quotation marks
402385
"""
403386

404387
# handle the special case/hardcoded terminator of a blank line
@@ -413,7 +396,7 @@ def parse(self, line: str) -> Statement:
413396
arg_list = []
414397

415398
# lex the input into a list of tokens
416-
tokens = self.tokenize(line)
399+
tokens = self.tokenize(line, expand)
417400

418401
# of the valid terminators, find the first one to occur in the input
419402
terminator_pos = len(tokens) + 1
@@ -594,6 +577,35 @@ def parse_command_only(self, rawinput: str) -> Statement:
594577
)
595578
return statement
596579

580+
def get_command_arg_list(self, command_name: str, to_parse: Union[Statement, str],
581+
preserve_quotes: bool) -> Tuple[Statement, List[str]]:
582+
"""
583+
Called by the argument_list and argparse wrappers to retrieve just the arguments being
584+
passed to their do_* methods as a list.
585+
586+
:param command_name: name of the command being run
587+
:param to_parse: what is being passed to the do_* method. It can be one of two types:
588+
1. An already parsed Statement
589+
2. An argument string in cases where a do_* method is explicitly called
590+
e.g.: Calling do_help('alias create') would cause to_parse to be 'alias create'
591+
592+
In this case, the string will be converted to a Statement and returned along
593+
with the argument list.
594+
595+
:param preserve_quotes: if True, then quotes will not be stripped from the arguments
596+
:return: A tuple containing:
597+
The Statement used to retrieve the arguments
598+
The argument list
599+
"""
600+
# Check if to_parse needs to be converted to a Statement
601+
if not isinstance(to_parse, Statement):
602+
to_parse = self.parse(command_name + ' ' + to_parse, expand=False)
603+
604+
if preserve_quotes:
605+
return to_parse, to_parse.arg_list
606+
else:
607+
return to_parse, to_parse.argv[1:]
608+
597609
def _expand(self, line: str) -> str:
598610
"""Expand shortcuts and aliases"""
599611

0 commit comments

Comments
 (0)