|
| 1 | +# a .fish complete file usually looks like a like |
| 2 | +# `complete -c command -n '__fish_seen_subcommand_from arg' -a arg -l long -s short -d 'description' |
| 3 | +# attempt to loosely pasrse it and convert to nu completions |
| 4 | + |
| 5 | +# parse every .fish file in the current directory and make a .nu completions file of it |
| 6 | +def build-completions-from-pwd [] { |
| 7 | + ls *.fish | par-each { |f| |
| 8 | + let out = ($f.name | str replace ".fish" ".nu") |
| 9 | + print $"building nushell completions from ($f.name)" |
| 10 | + build-completion $f.name $out |
| 11 | + } |
| 12 | +} |
| 13 | + |
| 14 | +# build a completion form a .fish file and generate a .nu file |
| 15 | +def build-completion [fish_file: path, nu_file: path] { |
| 16 | + open $fish_file | parse-fish | make-commands-completion | str join "\n\n" | save $nu_file |
| 17 | +} |
| 18 | + |
| 19 | +# parse a .fish file based on autogenerated complete syntax |
| 20 | +# returns a table of flags to arguments |
| 21 | +# currently only handles complete's args that don't use boolean flags (e.g. -f) |
| 22 | +def parse-fish [] { |
| 23 | + let data = ( |
| 24 | + $in | tokenize-complete-lines |
| 25 | + | where (($it | length) mod 2) == 1 # currently we only support complete args that all have args (pairs). including 'complete' this means an odd number of tokens |
| 26 | + | each { |tokens| $tokens | pair-args } # turn the tokens into a list of pairs |
| 27 | + | flatten # merge them all into a top level label |
| 28 | + ) |
| 29 | + # default every column in the table to "" to make processing easier |
| 30 | + # some values having null often breaks nu or requires lots of checking |
| 31 | + $data | columns | reduce -f $data { |c, acc| |
| 32 | + $acc | default "" $c |
| 33 | + } |
| 34 | + | default "" a |
| 35 | + | cleanup_subcommands # clean garbage subcommands |
| 36 | +} |
| 37 | + |
| 38 | +# tokenize each line of the fish file into a list of tokens |
| 39 | +# make use of detect columns -n which with one like properly tokenizers arguments including across quotes |
| 40 | +def tokenize-complete-lines [] { |
| 41 | + lines |
| 42 | + | each { |line| |
| 43 | + $line |
| 44 | + | where $line starts-with complete |
| 45 | + | str replace -a "\\\\'" "" # remove escaped quotes ' which break detect columns |
| 46 | + | str replace -a "-f " "" # remove -f which is a boolean flag we don't support yet |
| 47 | + | detect columns -n |
| 48 | + | transpose -i tokens # turn columns into items, each is a token |
| 49 | + } |
| 50 | + | where ($it | length) > 0 # remove any empty lines |
| 51 | + | get tokens # get the list of tokens |
| 52 | +} |
| 53 | + |
| 54 | +# turn a list of tokens for a line into a record of {flag: arg} |
| 55 | +def pair-args [] { |
| 56 | + where $it != complete # drop complete command as we don't need it |
| 57 | + | window 2 -s 2 # group by ordered pairs, using window 2 -s 2 instead of group 2 to automatically drop any left overs |
| 58 | + | each { |pair| |
| 59 | + [ |
| 60 | + {$"($pair.0 | str trim -c '-')": ($pair.1 | unquote)} # turn into a [{<flag> :<arg>}] removing quotes |
| 61 | + ] |
| 62 | + } |
| 63 | + | reduce { |it, acc| $acc | merge $it } # merge the list of records into one big record |
| 64 | +} |
| 65 | + |
| 66 | +def unquote [] { |
| 67 | + str trim -c "\'" # trim ' |
| 68 | + | str trim -c "\"" # trim " |
| 69 | +} |
| 70 | + |
| 71 | +# remove any entries which contain things in subcommands that may be fish functions or incorrect parses |
| 72 | +def cleanup_subcommands [] { |
| 73 | + where (not ($it.a | str contains "$")) and (not ($it.a | str starts-with "-")) and (not ($it.a starts-with "(")) |
| 74 | +} |
| 75 | + |
| 76 | +# from a parsed fish table, create the completion for it's command and sub commands |
| 77 | +def make-commands-completion [] { |
| 78 | + let fishes = $in |
| 79 | + $fishes |
| 80 | + | get c # c is the command name |
| 81 | + | uniq # is cloned on every complete line |
| 82 | + | each { |command| |
| 83 | + $fishes | where c == $command | make-subcommands-completion $command |
| 84 | + } |
| 85 | +} |
| 86 | + |
| 87 | +# make the action nu completion string from subcommand and args |
| 88 | +# subcommand can be empty which will be the root command |
| 89 | +def make-subcommands-completion [parents: list] { |
| 90 | + let quote = '"' # " |
| 91 | + let fishes = $in |
| 92 | + $fishes |
| 93 | + | group-by a # group by sub command (a flag) |
| 94 | + | transpose name args # turn it into a table of name to arguments |
| 95 | + | each {|subcommand| |
| 96 | + build-string ( |
| 97 | + if ('d' in ($subcommand.args | columns)) and ($subcommand.args.d != "") { |
| 98 | + build-string "# " ($subcommand.args.d.0) "\n" # (sub)command description |
| 99 | + }) "extern " $quote ($parents | str join " ") ( |
| 100 | + if $subcommand.name != "" { |
| 101 | + build-string " " $subcommand.name # sub command if present |
| 102 | + }) $quote " [\n" ( |
| 103 | + $fishes |
| 104 | + | if ('n' in ($in | columns)) { |
| 105 | + if ($subcommand.name != "") { |
| 106 | + where ($it.n | str contains $subcommand.name) # for subcommand -> any where n matches `__fish_seen_subcommand_from arg` for the subcommand name |
| 107 | + } else { |
| 108 | + where ($it.n == "__fish_use_subcommand") and ($it.a == "") # for root command -> any where n == __fish_use_subcommand and a is empty. otherwise a means a subcommand |
| 109 | + } |
| 110 | + } else { |
| 111 | + $fishes # catch all |
| 112 | + } |
| 113 | + | build-flags |
| 114 | + | str join "\n" |
| 115 | + ) "\n\t...args\n]" |
| 116 | + } |
| 117 | +} |
| 118 | + |
| 119 | +# build the list of flag string in nu syntax |
| 120 | +def build-flags [] { |
| 121 | + each { |subargs| |
| 122 | + if ('l' in ($subargs | columns)) and ($subargs.l != "") { |
| 123 | + build-string "\t--" $subargs.l (build-string |
| 124 | + (if ('s' in ($subargs | columns)) and ($subargs.s != "") { |
| 125 | + build-string "(-" $subargs.s ")" |
| 126 | + }) (if ('d' in ($subargs | columns)) and ($subargs.d != "") { |
| 127 | + build-string "\t\t\t\t\t# " $subargs.d |
| 128 | + }) |
| 129 | + ) |
| 130 | + } |
| 131 | + } |
| 132 | +} |
0 commit comments