Skip to content

Commit e824877

Browse files
authored
Add completions for lftp (#1113)
[lftp](https://lftp.yar.ru/) is the program which I use when I need to copy many files at once via SSH. Though having `FTP` in the name, it supports more protocols than just FTP. This PR provides auto-completion for the "site" argument. Source of sites are: - SSH aliases (for SFTP and FISH protocols). - LFTP's bookmarks. ![image](https://github.com/user-attachments/assets/4c6c5876-069b-454c-9e49-5e43c9061a52)
1 parent c0e88c5 commit e824877

File tree

2 files changed

+129
-0
lines changed

2 files changed

+129
-0
lines changed

custom-completions/lftp/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# LFTP completions
2+
3+
[`LFTP`](https://lftp.yar.ru/) is a file transfer program supporting many protocols (FTP, HTTP, SFTP, FISH, TORRENT). It is shell-like, reliable and can transfer several files in parallel.
4+
5+
## Install completion script
6+
7+
### Method 1: Use from Git repo
8+
9+
- `git clone https://github.com/nushell/nu_scripts.git`
10+
11+
- Add this to `$nu.config-path` file.
12+
13+
```nu
14+
source repo/custom-completions/lftp/lftp-completions.nu
15+
```
16+
17+
### Method 2: Selectively copy
18+
19+
20+
- Copy the _lftp-completions.nu_ to Nu standard place.
21+
22+
```nu
23+
cp custom-completions/lftp/lftp-completions.nu ($nu.data-dir | path join 'completions')
24+
```
25+
26+
- Open `$nu.config-path` file and add this:
27+
28+
```nu
29+
source lftp-completions.nu
30+
```
31+
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# nu-version: 0.102.0
2+
3+
module lftp-completion-utils {
4+
def extract-host []: list<string> -> record<name: string, addr: string> {
5+
# Host is a list of lines, like:
6+
# ╭───┬──────────────────────────────╮
7+
# │ 0 │ Host quanweb │
8+
# │ 1 │ User quan │
9+
# │ 2 │ Hostname quan.hoabinh.vn │
10+
# │ 3 │ # ProxyJump farmb-omz │
11+
# │ 4 │ │
12+
# ╰───┴──────────────────────────────╯
13+
let host = $in
14+
let $first_line = try { $host | first | str trim } catch { null }
15+
# Don't accept blocks like "Host *"
16+
if ($first_line | is-empty) or '*' in $first_line {
17+
null
18+
} else {
19+
let name = $first_line | split row -r '\s+' | get 1
20+
# May not contain hostname
21+
match ($host | slice 1.. | find -ir '^\s*Hostname\s') {
22+
[] => null,
23+
$addr => { name: $name, addr: ($addr | str trim | split row -n 2 -r '\s+' | get 1) }
24+
}
25+
}
26+
}
27+
28+
# Process a SSH config file
29+
export def process []: string -> record<hosts: list<record<name: string, addr: string>>, includes: list<string>> {
30+
let lines = $in | lines
31+
# Get 'Include' lines
32+
let include_lines = $lines | find -n -ir '^Include\s' | str trim | each { $in | split row -n 2 -r '\s+' | get 1 | str trim -c '"'}
33+
# Find "Host" blocks
34+
let marks = $lines | enumerate | find -n -ir '^Host\s'
35+
let mark_indices = $marks | get index | append ($lines | length)
36+
let hosts = $mark_indices | window 2 | each {|w| $lines | slice $w.0..<($w.1) }
37+
{
38+
hosts: ($hosts | each { $in | extract-host }),
39+
includes: $include_lines
40+
}
41+
}
42+
43+
export def get-ssh-sites []: nothing -> table<value: string, description: string> {
44+
let files = [
45+
'/etc/ssh/ssh_config',
46+
'~/.ssh/config'
47+
] | filter {|file| $file | path exists }
48+
49+
50+
let first_result: record<hosts: list<record<name: string, addr: string>>, includes: list<string>> = $files | par-each {|file|
51+
let folder = $file | path expand | path dirname
52+
let r = $file | open --raw | process
53+
$r | update includes { each {|f| $folder | path join $f } }
54+
} | reduce {|it| merge deep $it --strategy=append }
55+
56+
let $includes: list<string> = $first_result.includes | each {|f|
57+
if '*' in $f {
58+
glob $f
59+
} else if ($f | path exists) {
60+
[$f]
61+
} else []
62+
} | flatten
63+
64+
# Process include files
65+
let included_hosts: list<record<name: string, addr: string>> = (if ($includes | is-empty) { [] } else {
66+
let second_result = $includes | par-each {|p| $p | open --raw | process } | reduce {|it| merge deep $it --strategy=append }
67+
$second_result.hosts
68+
})
69+
70+
let hosts = $first_result.hosts ++ $included_hosts
71+
$hosts | each {|h| [$"sftp://($h.name)" $"fish://($h.name)"] | wrap value | insert description $h.addr } | flatten
72+
}
73+
74+
export def get-bookmark-sites []: nothing -> table<value: string, description: string> {
75+
const FILE_PATH = '~/.local/share/lftp/bookmarks' | path expand
76+
let sites = open --raw $FILE_PATH | lines | split column -n 2 -r \s+ value description
77+
$sites | update value { $"bm:($in)" }
78+
}
79+
}
80+
81+
82+
def "nu-complete lftp-site" [] {
83+
use lftp-completion-utils get-ssh-sites
84+
use lftp-completion-utils get-bookmark-sites
85+
get-ssh-sites | append (get-bookmark-sites)
86+
}
87+
88+
export extern 'lftp' [
89+
-c: string # Execute the commands and exit
90+
-d # Switch on debugging mode
91+
-e: string # Execute the command just after selecting
92+
-p: int # Use the port for connection
93+
-u: string # Use the user/password for authentication
94+
--norc # Don't execute rc files from the home directory
95+
--help # Print the help and exit
96+
--version # Print lftp version and exit
97+
site?: string@"nu-complete lftp-site" # Host name, URL or bookmark name
98+
]

0 commit comments

Comments
 (0)