1- """Bridges calls made inside of pyscript with the Cmd2 host app while maintaining a reasonable
2- degree of isolation between the two"""
1+ """
2+ Bridges calls made inside of pyscript with the Cmd2 host app while maintaining a reasonable
3+ degree of isolation between the two
4+
5+ Copyright 2018 Eric Lin <[email protected] > 6+ Released under MIT license, see LICENSE file
7+ """
38
49import argparse
10+ from typing import List , Tuple
11+
512
613class ArgparseFunctor :
714 def __init__ (self , cmd2_app , item , parser ):
815 self ._cmd2_app = cmd2_app
916 self ._item = item
1017 self ._parser = parser
1118
19+ # Dictionary mapping command argument name to value
1220 self ._args = {}
21+ # argparse object for the current command layer
1322 self .__current_subcommand_parser = parser
1423
1524 def __getattr__ (self , item ):
16- # look for sub-command
25+ """Search for a subcommand matching this item and update internal state to track the traversal"""
26+ # look for sub-command under the current command/sub-command layer
1727 for action in self .__current_subcommand_parser ._actions :
1828 if not action .option_strings and isinstance (action , argparse ._SubParsersAction ):
1929 if item in action .choices :
@@ -22,27 +32,38 @@ def __getattr__(self, item):
2232 self .__current_subcommand_parser = action .choices [item ]
2333 self ._args [action .dest ] = item
2434 return self
35+
2536 return super ().__getatttr__ (item )
2637
2738 def __call__ (self , * args , ** kwargs ):
39+ """
40+ Process the arguments at this layer of the argparse command tree. If there are more sub-commands,
41+ return self to accept the next sub-command name. If there are no more sub-commands, execute the
42+ sub-command with the given parameters.
43+ """
2844 next_pos_index = 0
2945
3046 has_subcommand = False
3147 consumed_kw = []
48+
49+ # Iterate through the current sub-command's arguments in order
3250 for action in self .__current_subcommand_parser ._actions :
3351 # is this a flag option?
3452 if action .option_strings :
53+ # this is a flag argument, search for the argument by name in the parameters
3554 if action .dest in kwargs :
3655 self ._args [action .dest ] = kwargs [action .dest ]
3756 consumed_kw .append (action .dest )
3857 else :
58+ # This is a positional argument, search the positional arguments passed in.
3959 if not isinstance (action , argparse ._SubParsersAction ):
4060 if next_pos_index < len (args ):
4161 self ._args [action .dest ] = args [next_pos_index ]
4262 next_pos_index += 1
4363 else :
4464 has_subcommand = True
4565
66+ # Check if there are any extra arguments we don't know how to handle
4667 for kw in kwargs :
4768 if kw not in consumed_kw :
4869 raise TypeError ('{}() got an unexpected keyword argument \' {}\' ' .format (
@@ -60,19 +81,38 @@ def _run(self):
6081 # reconstruct the cmd2 command from the python call
6182 cmd_str = ['' ]
6283
84+ def process_flag (action , value ):
85+ # was the argument a flag?
86+ if action .option_strings :
87+ cmd_str [0 ] += '{} ' .format (action .option_strings [0 ])
88+
89+ if isinstance (value , List ) or isinstance (value , Tuple ):
90+ for item in value :
91+ item = str (item ).strip ()
92+ if ' ' in item :
93+ item = '"{}"' .format (item )
94+ cmd_str [0 ] += '{} ' .format (item )
95+ else :
96+ value = str (value ).strip ()
97+ if ' ' in value :
98+ value = '"{}"' .format (value )
99+ cmd_str [0 ] += '{} ' .format (value )
100+
63101 def traverse_parser (parser ):
64102 for action in parser ._actions :
65103 # was something provided for the argument
66104 if action .dest in self ._args :
67105 if isinstance (action , argparse ._SubParsersAction ):
106+ cmd_str [0 ] += '{} ' .format (self ._args [action .dest ])
68107 traverse_parser (action .choices [self ._args [action .dest ]])
108+ elif isinstance (action , argparse ._AppendAction ):
109+ if isinstance (self ._args [action .dest ], List ) or isinstance (self ._args [action .dest ], Tuple ):
110+ for values in self ._args [action .dest ]:
111+ process_flag (action , values )
112+ else :
113+ process_flag (action , self ._args [action .dest ])
69114 else :
70- # was the argument a flag?
71- if action .option_strings :
72- cmd_str [0 ] += '{} ' .format (action .option_strings [0 ])
73-
74- # TODO: Handle 'narg' and 'append' options
75- cmd_str [0 ] += '"{}" ' .format (self ._args [action .dest ])
115+ process_flag (action , self ._args [action .dest ])
76116
77117 traverse_parser (self ._parser )
78118
@@ -81,23 +121,29 @@ def traverse_parser(parser):
81121
82122
83123class PyscriptBridge (object ):
124+ """Preserves the legacy 'cmd' interface for pyscript while also providing a new python API wrapper for
125+ application commands."""
84126 def __init__ (self , cmd2_app ):
85127 self ._cmd2_app = cmd2_app
86128 self ._last_result = None
87129
88130 def __getattr__ (self , item : str ):
131+ """Check if the attribute is a command. If so, return a callable."""
89132 commands = self ._cmd2_app .get_all_commands ()
90133 if item in commands :
91134 func = getattr (self ._cmd2_app , 'do_' + item )
92135
93136 try :
137+ # See if the command uses argparse
94138 parser = getattr (func , 'argparser' )
95139 except AttributeError :
140+ # Command doesn't, we will accept parameters in the form of a command string
96141 def wrap_func (args = '' ):
97142 func (args )
98143 return self ._cmd2_app ._last_result
99144 return wrap_func
100145 else :
146+ # Command does use argparse, return an object that can traverse the argparse subcommands and arguments
101147 return ArgparseFunctor (self ._cmd2_app , item , parser )
102148
103149 return super ().__getattr__ (item )
0 commit comments