|
13 | 13 | _StoreConstAction, |
14 | 14 | _VersionAction, |
15 | 15 | ) |
| 16 | +from collections import defaultdict |
16 | 17 | from functools import total_ordering |
17 | 18 | from string import Template |
18 | 19 |
|
|
32 | 33 | SUPPORTED_SHELLS = [] |
33 | 34 | _SUPPORTED_COMPLETERS = {} |
34 | 35 | CHOICE_FUNCTIONS = { |
35 | | - "file": {"bash": "_shtab_compgen_files", "zsh": "_files"}, |
36 | | - "directory": {"bash": "_shtab_compgen_dirs", "zsh": "_files -/"}} |
| 36 | + "file": {"bash": "_shtab_compgen_files", "zsh": "_files", "tcsh": "f"}, |
| 37 | + "directory": {"bash": "_shtab_compgen_dirs", "zsh": "_files -/", "tcsh": "d"}} |
37 | 38 | FILE = CHOICE_FUNCTIONS["file"] |
38 | 39 | DIRECTORY = DIR = CHOICE_FUNCTIONS["directory"] |
39 | 40 | FLAG_OPTION = ( |
@@ -580,6 +581,109 @@ def format_positional(opt): |
580 | 581 | ) |
581 | 582 |
|
582 | 583 |
|
| 584 | +@mark_completer("tcsh") |
| 585 | +def complete_tcsh(parser, root_prefix=None, preamble="", choice_functions=None): |
| 586 | + """ |
| 587 | + Return tcsh syntax autocompletion script. |
| 588 | +
|
| 589 | + See `complete` for arguments. |
| 590 | + """ |
| 591 | + optionals_single = set() |
| 592 | + optionals_double = set() |
| 593 | + specials = [] |
| 594 | + index_choices = defaultdict(dict) |
| 595 | + |
| 596 | + choice_type2fn = {k: v["tcsh"] for k, v in CHOICE_FUNCTIONS.items()} |
| 597 | + if choice_functions: |
| 598 | + choice_type2fn.update(choice_functions) |
| 599 | + |
| 600 | + def get_specials(arg, arg_type, arg_sel): |
| 601 | + if arg.choices: |
| 602 | + choice_strs = ' '.join(map(str, arg.choices)) |
| 603 | + yield "'{}/{}/({})/'".format( |
| 604 | + arg_type, |
| 605 | + arg_sel, |
| 606 | + choice_strs, |
| 607 | + ) |
| 608 | + elif hasattr(arg, "complete"): |
| 609 | + complete_fn = complete2pattern(arg.complete, 'tcsh', choice_type2fn) |
| 610 | + if complete_fn: |
| 611 | + yield "'{}/{}/{}/'".format( |
| 612 | + arg_type, |
| 613 | + arg_sel, |
| 614 | + complete_fn, |
| 615 | + ) |
| 616 | + |
| 617 | + def recurse_parser(cparser, positional_idx, requirements=None): |
| 618 | + log_prefix = '| ' * positional_idx |
| 619 | + log.debug('%sParser @ %d', log_prefix, positional_idx) |
| 620 | + if requirements: |
| 621 | + log.debug('%s- Requires: %s', log_prefix, ' '.join(requirements)) |
| 622 | + else: |
| 623 | + requirements = [] |
| 624 | + |
| 625 | + for optional in cparser._get_optional_actions(): |
| 626 | + log.debug('%s| Optional: %s', log_prefix, optional.dest) |
| 627 | + # Mingle all optional arguments for all subparsers |
| 628 | + for optional_str in optional.option_strings: |
| 629 | + log.debug('%s| | %s', log_prefix, optional_str) |
| 630 | + if optional_str.startswith('--'): |
| 631 | + optionals_double.add(optional_str[2:]) |
| 632 | + elif optional_str.startswith('-'): |
| 633 | + optionals_single.add(optional_str[1:]) |
| 634 | + specials.extend(get_specials(optional, 'n', optional_str)) |
| 635 | + |
| 636 | + for positional in cparser._get_positional_actions(): |
| 637 | + if positional.help != SUPPRESS: |
| 638 | + positional_idx += 1 |
| 639 | + log.debug('%s| Positional #%d: %s', log_prefix, positional_idx, positional.dest) |
| 640 | + index_choices[positional_idx][tuple(requirements)] = positional |
| 641 | + if not requirements and isinstance(positional.choices, dict): |
| 642 | + for subcmd, subparser in positional.choices.items(): |
| 643 | + log.debug('%s| | SubParser: %s', log_prefix, subcmd) |
| 644 | + recurse_parser(subparser, positional_idx, requirements + [subcmd]) |
| 645 | + |
| 646 | + recurse_parser(parser, 0) |
| 647 | + |
| 648 | + for idx, ndict in index_choices.items(): |
| 649 | + if len(ndict) == 1: |
| 650 | + # Single choice, no requirements |
| 651 | + arg = list(ndict.values())[0] |
| 652 | + specials.extend(get_specials(arg, 'p', str(idx))) |
| 653 | + else: |
| 654 | + # Multiple requirements |
| 655 | + nlist = [] |
| 656 | + for nn, arg in ndict.items(): |
| 657 | + checks = [ |
| 658 | + '[ "$cmd[{}]" == "{}" ]'.format(iidx, n) for iidx, n in enumerate(nn, start=2)] |
| 659 | + if arg.choices: |
| 660 | + nlist.append('( {}echo "{}" || false )'.format( |
| 661 | + ' && '.join(checks + ['']), # Append the separator |
| 662 | + '\\n'.join(arg.choices), |
| 663 | + )) |
| 664 | + |
| 665 | + # Ugly hack |
| 666 | + specials.append("'p@{}@`set cmd=($COMMAND_LINE); {}`@'".format( |
| 667 | + str(idx), ' || '.join(nlist))) |
| 668 | + |
| 669 | + return Template("""\ |
| 670 | +#!/usr/bin/env tcsh |
| 671 | +# AUTOMATICALLY GENERATED by `shtab` |
| 672 | +
|
| 673 | +${preamble} |
| 674 | +
|
| 675 | +complete ${prog} \\ |
| 676 | + 'c/--/(${optionals_double_str})/' \\ |
| 677 | + 'c/-/(${optionals_single_str} -)/' \\ |
| 678 | + ${optionals_special_str} \\ |
| 679 | + 'p/*/()/'""").safe_substitute( |
| 680 | + preamble=("\n# Custom Preamble\n" + preamble + |
| 681 | + "\n# End Custom Preamble\n" if preamble else ""), root_prefix=root_prefix, |
| 682 | + prog=parser.prog, optionals_double_str=' '.join(optionals_double), |
| 683 | + optionals_single_str=' '.join(optionals_single), |
| 684 | + optionals_special_str=' \\\n '.join(specials)) |
| 685 | + |
| 686 | + |
583 | 687 | def complete(parser, shell="bash", root_prefix=None, preamble="", choice_functions=None): |
584 | 688 | """ |
585 | 689 | parser : argparse.ArgumentParser |
|
0 commit comments