@@ -30,35 +30,74 @@ export extern "ssh" [
3030 - o : string # option
3131]
3232
33+ module ssh-completion-utils {
34+ def extract-host []: list <string > -> record <name : string , addr : string > {
35+ # Host is a list of lines, like:
36+ # ╭───┬──────────────────────────────╮
37+ # │ 0 │ Host quanweb │
38+ # │ 1 │ User quan │
39+ # │ 2 │ Hostname quan.hoabinh.vn │
40+ # │ 3 │ # ProxyJump farmb-omz │
41+ # │ 4 │ │
42+ # ╰───┴──────────────────────────────╯
43+ let host = $in
44+ let name = $host | get 0 | str trim | split row - r ' \s+' | get 1
45+ # Don't accept blocks like "Host *"
46+ if (' *' in $name ) {
47+ null
48+ } else {
49+ # May not contain hostname
50+ match ($host | slice 1 .. | find - ir ' ^\s*Hostname\s' ) {
51+ [] => null ,
52+ $addr => { name : $name , addr : ($addr | str trim | split row - n 2 - r ' \s+' | get 1 ) }
53+ }
54+ }
55+ }
56+
57+ # Process a SSH config file
58+ export def process []: string -> record <hosts : list <record <name : string , addr : string >>, includes : list <string >> {
59+ let lines = $in | lines
60+ # Get 'Include' lines
61+ let include_lines = $lines | find - n - ir ' ^Include\s' | str trim | each { $in | split row - n 2 - r ' \s+' | get 1 | str trim - c ' "' }
62+ # Find "Host" blocks
63+ let marks = $lines | enumerate | find - n - ir ' ^Host\s'
64+ let mark_indices = $marks | get index | append ($lines | length )
65+ let hosts = $mark_indices | window 2 | each {|w | $lines | slice $w.0 ..< ($w.1 ) }
66+ {
67+ hosts : ($hosts | each { $in | extract-host }),
68+ includes : $include_lines
69+ }
70+ }
71+
72+ }
73+
74+
3375def "nu-complete ssh-host" [] {
3476 let files = [
3577 ' /etc/ssh/ssh_config' ,
3678 ' ~/.ssh/config'
3779 ] | filter {|file | $file | path exists }
3880
39- let included_files = $files | each {|file |
40- let folder = $file | path expand | path dirname
41- let rel_subfiles = $file | open | lines | str trim | where { |s | $s | str starts-with ' Include' } | each { |s | $s | parse -- regex ' ^Include\s+(?<subfile>.+)' | get subfile | str replace - a ' "' ' ' } | flatten
42- $rel_subfiles | each { |f | $folder | path join $f }
43- } | flatten | filter { |p | $p | path exists }
81+ use ssh-completion-utils process
4482
45-
46- [ ... $files , ... $included_files ] | each {|file |
47- let lines = $file | open | lines | str trim
48-
49- mut result = []
50- for $line in $lines {
51- let data = $line | parse -- regex ' ^Host\s+(?<host>[-\.\w]+)'
52- if ($data | is-not-empty ) {
53- $result = ($result | append { 'value' : ($data.host | first ), 'description' : " " })
54- continue ;
55- }
56- let data = $line | parse -- regex ' ^HostName\s+(?<hostname>.+)'
57- if ($data | is-not-empty ) {
58- let last = $result | last | update ' description' ($data.hostname | first )
59- $result = ($result | drop | append $last )
60- }
61- }
62- $result
83+ let first_result = $files | par-each {|file |
84+ let folder = $file | path expand | path dirname
85+ mut r = $file | open -- raw | process
86+ $r.includes = $r.includes | each {|f | $folder | path join $f }
87+ $r
88+ } | reduce {|it | merge deep $it -- strategy=append }
89+ let hosts = $first_result.hosts
90+ let $includes : list<string> = $first_result.includes | each {|f |
91+ if ' *' in $f {
92+ glob $f
93+ } else if ($f | path exists ) {
94+ [$f ]
95+ } else []
6396 } | flatten
97+
98+ # Process include files
99+ let second_result = $includes | par-each {|p | $p | open -- raw | process } | reduce {|it | merge deep $it -- strategy=append }
100+ # We don't further process "Include" lines in these secondary files.
101+ let hosts = $hosts ++ $second_result.hosts
102+ $hosts | each { {value : $in.name , description : $in.addr } }
64103}
0 commit comments