|
7 | 7 | import numbers
|
8 | 8 | from typing import (
|
9 | 9 | List,
|
| 10 | + cast, |
10 | 11 | )
|
11 | 12 |
|
12 | 13 | import pytest
|
@@ -329,6 +330,7 @@ def do_standalone(self, args: argparse.Namespace) -> None:
|
329 | 330 | @pytest.fixture
|
330 | 331 | def ac_app():
|
331 | 332 | app = ArgparseCompleterTester()
|
| 333 | + # noinspection PyTypeChecker |
332 | 334 | app.stdout = StdSim(app.stdout)
|
333 | 335 | return app
|
334 | 336 |
|
@@ -1156,52 +1158,171 @@ def test_complete_standalone(ac_app, flag, completions):
|
1156 | 1158 | assert ac_app.completion_matches == sorted(completions, key=ac_app.default_sort_key)
|
1157 | 1159 |
|
1158 | 1160 |
|
| 1161 | +# Custom ArgparseCompleter-based class |
1159 | 1162 | class CustomCompleter(argparse_completer.ArgparseCompleter):
|
1160 | 1163 | def _complete_flags(self, text: str, line: str, begidx: int, endidx: int, matched_flags: List[str]) -> List[str]:
|
1161 |
| - """Override so arguments with 'always_complete' set to True will always be completed""" |
1162 |
| - for flag in matched_flags: |
| 1164 | + """Override so flags with 'complete_when_ready' set to True will complete only when app is ready""" |
| 1165 | + |
| 1166 | + # Find flags which should not be completed and place them in matched_flags |
| 1167 | + for flag in self._flags: |
1163 | 1168 | action = self._flag_to_action[flag]
|
1164 |
| - if action.get_always_complete() is True: |
1165 |
| - matched_flags.remove(flag) |
| 1169 | + app: CustomCompleterApp = cast(CustomCompleterApp, self._cmd2_app) |
| 1170 | + if action.get_complete_when_ready() is True and not app.is_ready: |
| 1171 | + matched_flags.append(flag) |
| 1172 | + |
1166 | 1173 | return super(CustomCompleter, self)._complete_flags(text, line, begidx, endidx, matched_flags)
|
1167 | 1174 |
|
1168 | 1175 |
|
1169 |
| -argparse_custom.register_argparse_argument_parameter('always_complete', bool) |
| 1176 | +# Add a custom argparse action attribute |
| 1177 | +argparse_custom.register_argparse_argument_parameter('complete_when_ready', bool) |
1170 | 1178 |
|
1171 | 1179 |
|
| 1180 | +# App used to test custom ArgparseCompleter types and custom argparse attributes |
1172 | 1181 | class CustomCompleterApp(cmd2.Cmd):
|
1173 |
| - _parser = Cmd2ArgumentParser(description="Testing manually wrapping") |
1174 |
| - _parser.add_argument('--myflag', always_complete=True, nargs=1) |
| 1182 | + def __init__(self): |
| 1183 | + super().__init__() |
| 1184 | + self.is_ready = True |
| 1185 | + |
| 1186 | + # Parser that's used to test setting the app-wide default ArgparseCompleter type |
| 1187 | + default_completer_parser = Cmd2ArgumentParser(description="Testing app-wide argparse completer") |
| 1188 | + default_completer_parser.add_argument('--myflag', complete_when_ready=True) |
| 1189 | + |
| 1190 | + @with_argparser(default_completer_parser) |
| 1191 | + def do_default_completer(self, args: argparse.Namespace) -> None: |
| 1192 | + """Test command""" |
| 1193 | + pass |
| 1194 | + |
| 1195 | + # Parser that's used to test setting a custom completer at the parser level |
| 1196 | + custom_completer_parser = Cmd2ArgumentParser( |
| 1197 | + description="Testing parser-specific argparse completer", ap_completer_type=CustomCompleter |
| 1198 | + ) |
| 1199 | + custom_completer_parser.add_argument('--myflag', complete_when_ready=True) |
| 1200 | + |
| 1201 | + @with_argparser(custom_completer_parser) |
| 1202 | + def do_custom_completer(self, args: argparse.Namespace) -> None: |
| 1203 | + """Test command""" |
| 1204 | + pass |
| 1205 | + |
| 1206 | + # Test as_subcommand_to decorator with custom completer |
| 1207 | + top_parser = Cmd2ArgumentParser(description="Top Command") |
| 1208 | + top_subparsers = top_parser.add_subparsers(dest='subcommand', metavar='SUBCOMMAND') |
| 1209 | + top_subparsers.required = True |
| 1210 | + |
| 1211 | + @with_argparser(top_parser) |
| 1212 | + def do_top(self, args: argparse.Namespace) -> None: |
| 1213 | + """Top level command""" |
| 1214 | + # Call handler for whatever subcommand was selected |
| 1215 | + handler = args.cmd2_handler.get() |
| 1216 | + handler(args) |
| 1217 | + |
| 1218 | + # Parser for a subcommand with no custom completer type |
| 1219 | + no_custom_completer_parser = Cmd2ArgumentParser(description="No custom completer") |
| 1220 | + no_custom_completer_parser.add_argument('--myflag', complete_when_ready=True) |
1175 | 1221 |
|
1176 |
| - @with_argparser(_parser) |
1177 |
| - def do_mycommand(self, cmd: 'CustomCompleterApp', args: argparse.Namespace) -> None: |
1178 |
| - """Test command that will be manually wrapped to use argparse""" |
1179 |
| - print(args) |
| 1222 | + @cmd2.as_subcommand_to('top', 'no_custom', no_custom_completer_parser, help="no custom completer") |
| 1223 | + def _subcmd_no_custom(self, args: argparse.Namespace) -> None: |
| 1224 | + pass |
| 1225 | + |
| 1226 | + # Parser for a subcommand with a custom completer type |
| 1227 | + custom_completer_parser = Cmd2ArgumentParser(description="Custom completer", ap_completer_type=CustomCompleter) |
| 1228 | + custom_completer_parser.add_argument('--myflag', complete_when_ready=True) |
| 1229 | + |
| 1230 | + @cmd2.as_subcommand_to('top', 'custom', custom_completer_parser, help="custom completer") |
| 1231 | + def _subcmd_custom(self, args: argparse.Namespace) -> None: |
| 1232 | + pass |
1180 | 1233 |
|
1181 | 1234 |
|
1182 | 1235 | @pytest.fixture
|
1183 | 1236 | def custom_completer_app():
|
1184 |
| - |
1185 |
| - argparse_completer.set_default_ap_completer_type(CustomCompleter) |
1186 | 1237 | app = CustomCompleterApp()
|
1187 |
| - app.stdout = StdSim(app.stdout) |
1188 |
| - yield app |
1189 |
| - argparse_completer.set_default_ap_completer_type(argparse_completer.ArgparseCompleter) |
| 1238 | + return app |
1190 | 1239 |
|
1191 | 1240 |
|
1192 |
| -@pytest.mark.parametrize( |
1193 |
| - 'command_and_args, text, output_contains, first_match', |
1194 |
| - [ |
1195 |
| - ('mycommand', '--my', '', '--myflag '), |
1196 |
| - ('mycommand --myflag 5', '--my', '', '--myflag '), |
1197 |
| - ], |
1198 |
| -) |
1199 |
| -def test_custom_completer_type(custom_completer_app, command_and_args, text, output_contains, first_match, capsys): |
1200 |
| - line = '{} {}'.format(command_and_args, text) |
| 1241 | +def test_default_custom_completer_type(custom_completer_app: CustomCompleterApp): |
| 1242 | + """Test altering the app-wide default ArgparseCompleter type""" |
| 1243 | + try: |
| 1244 | + argparse_completer.set_default_ap_completer_type(CustomCompleter) |
| 1245 | + |
| 1246 | + text = '--m' |
| 1247 | + line = f'default_completer {text}' |
| 1248 | + endidx = len(line) |
| 1249 | + begidx = endidx - len(text) |
| 1250 | + |
| 1251 | + # The flag should complete because app is ready |
| 1252 | + custom_completer_app.is_ready = True |
| 1253 | + assert complete_tester(text, line, begidx, endidx, custom_completer_app) is not None |
| 1254 | + assert custom_completer_app.completion_matches == ['--myflag '] |
| 1255 | + |
| 1256 | + # The flag should not complete because app is not ready |
| 1257 | + custom_completer_app.is_ready = False |
| 1258 | + assert complete_tester(text, line, begidx, endidx, custom_completer_app) is None |
| 1259 | + assert not custom_completer_app.completion_matches |
| 1260 | + |
| 1261 | + finally: |
| 1262 | + # Restore the default completer |
| 1263 | + argparse_completer.set_default_ap_completer_type(argparse_completer.ArgparseCompleter) |
| 1264 | + |
| 1265 | + |
| 1266 | +def test_custom_completer_type(custom_completer_app: CustomCompleterApp): |
| 1267 | + """Test parser with a specific custom ArgparseCompleter type""" |
| 1268 | + text = '--m' |
| 1269 | + line = f'custom_completer {text}' |
1201 | 1270 | endidx = len(line)
|
1202 | 1271 | begidx = endidx - len(text)
|
1203 | 1272 |
|
1204 |
| - assert first_match == complete_tester(text, line, begidx, endidx, custom_completer_app) |
| 1273 | + # The flag should complete because app is ready |
| 1274 | + custom_completer_app.is_ready = True |
| 1275 | + assert complete_tester(text, line, begidx, endidx, custom_completer_app) is not None |
| 1276 | + assert custom_completer_app.completion_matches == ['--myflag '] |
1205 | 1277 |
|
1206 |
| - out, err = capsys.readouterr() |
1207 |
| - assert output_contains in out |
| 1278 | + # The flag should not complete because app is not ready |
| 1279 | + custom_completer_app.is_ready = False |
| 1280 | + assert complete_tester(text, line, begidx, endidx, custom_completer_app) is None |
| 1281 | + assert not custom_completer_app.completion_matches |
| 1282 | + |
| 1283 | + |
| 1284 | +def test_decorated_subcmd_custom_completer(custom_completer_app: CustomCompleterApp): |
| 1285 | + """Tests custom completer type on a subcommand created with @cmd2.as_subcommand_to""" |
| 1286 | + |
| 1287 | + # First test the subcommand without the custom completer |
| 1288 | + text = '--m' |
| 1289 | + line = f'top no_custom {text}' |
| 1290 | + endidx = len(line) |
| 1291 | + begidx = endidx - len(text) |
| 1292 | + |
| 1293 | + # The flag should complete regardless of ready state since this subcommand isn't using the custom completer |
| 1294 | + custom_completer_app.is_ready = True |
| 1295 | + assert complete_tester(text, line, begidx, endidx, custom_completer_app) is not None |
| 1296 | + assert custom_completer_app.completion_matches == ['--myflag '] |
| 1297 | + |
| 1298 | + custom_completer_app.is_ready = False |
| 1299 | + assert complete_tester(text, line, begidx, endidx, custom_completer_app) is not None |
| 1300 | + assert custom_completer_app.completion_matches == ['--myflag '] |
| 1301 | + |
| 1302 | + # Now test the subcommand with the custom completer |
| 1303 | + text = '--m' |
| 1304 | + line = f'top custom {text}' |
| 1305 | + endidx = len(line) |
| 1306 | + begidx = endidx - len(text) |
| 1307 | + |
| 1308 | + # The flag should complete because app is ready |
| 1309 | + custom_completer_app.is_ready = True |
| 1310 | + assert complete_tester(text, line, begidx, endidx, custom_completer_app) is not None |
| 1311 | + assert custom_completer_app.completion_matches == ['--myflag '] |
| 1312 | + |
| 1313 | + # The flag should not complete because app is not ready |
| 1314 | + custom_completer_app.is_ready = False |
| 1315 | + assert complete_tester(text, line, begidx, endidx, custom_completer_app) is None |
| 1316 | + assert not custom_completer_app.completion_matches |
| 1317 | + |
| 1318 | + |
| 1319 | +def test_add_parser_custom_completer(): |
| 1320 | + """Tests setting a custom completer type on a subcommand using add_parser()""" |
| 1321 | + parser = Cmd2ArgumentParser() |
| 1322 | + subparsers = parser.add_subparsers() |
| 1323 | + |
| 1324 | + no_custom_completer_parser = subparsers.add_parser(name="no_custom_completer") |
| 1325 | + assert no_custom_completer_parser.get_ap_completer_type() is None # type: ignore[attr-defined] |
| 1326 | + |
| 1327 | + custom_completer_parser = subparsers.add_parser(name="no_custom_completer", ap_completer_type=CustomCompleter) |
| 1328 | + assert custom_completer_parser.get_ap_completer_type() is CustomCompleter # type: ignore[attr-defined] |
0 commit comments