From e82487722bce88c11f6b31788142b29e4c40bda4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20H=E1=BB=93ng=20Qu=C3=A2n?= Date: Sun, 4 May 2025 04:14:52 +0700 Subject: [PATCH] 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) --- custom-completions/lftp/README.md | 31 +++++++ custom-completions/lftp/lftp-completions.nu | 98 +++++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 custom-completions/lftp/README.md create mode 100644 custom-completions/lftp/lftp-completions.nu diff --git a/custom-completions/lftp/README.md b/custom-completions/lftp/README.md new file mode 100644 index 0000000..8a2fb4e --- /dev/null +++ b/custom-completions/lftp/README.md @@ -0,0 +1,31 @@ +# LFTP completions + +[`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. + +## Install completion script + +### Method 1: Use from Git repo + +- `git clone https://github.com/nushell/nu_scripts.git` + +- Add this to `$nu.config-path` file. + + ```nu + source repo/custom-completions/lftp/lftp-completions.nu + ``` + +### Method 2: Selectively copy + + +- Copy the _lftp-completions.nu_ to Nu standard place. + + ```nu + cp custom-completions/lftp/lftp-completions.nu ($nu.data-dir | path join 'completions') + ``` + +- Open `$nu.config-path` file and add this: + + ```nu + source lftp-completions.nu + ``` + diff --git a/custom-completions/lftp/lftp-completions.nu b/custom-completions/lftp/lftp-completions.nu new file mode 100644 index 0000000..9611133 --- /dev/null +++ b/custom-completions/lftp/lftp-completions.nu @@ -0,0 +1,98 @@ +# nu-version: 0.102.0 + +module lftp-completion-utils { + def extract-host []: list -> record { + # Host is a list of lines, like: + # ╭───┬──────────────────────────────╮ + # │ 0 │ Host quanweb │ + # │ 1 │ User quan │ + # │ 2 │ Hostname quan.hoabinh.vn │ + # │ 3 │ # ProxyJump farmb-omz │ + # │ 4 │ │ + # ╰───┴──────────────────────────────╯ + let host = $in + let $first_line = try { $host | first | str trim } catch { null } + # Don't accept blocks like "Host *" + if ($first_line | is-empty) or '*' in $first_line { + null + } else { + let name = $first_line | split row -r '\s+' | get 1 + # May not contain hostname + match ($host | slice 1.. | find -ir '^\s*Hostname\s') { + [] => null, + $addr => { name: $name, addr: ($addr | str trim | split row -n 2 -r '\s+' | get 1) } + } + } + } + + # Process a SSH config file + export def process []: string -> record>, includes: list> { + let lines = $in | lines + # Get 'Include' lines + let include_lines = $lines | find -n -ir '^Include\s' | str trim | each { $in | split row -n 2 -r '\s+' | get 1 | str trim -c '"'} + # Find "Host" blocks + let marks = $lines | enumerate | find -n -ir '^Host\s' + let mark_indices = $marks | get index | append ($lines | length) + let hosts = $mark_indices | window 2 | each {|w| $lines | slice $w.0..<($w.1) } + { + hosts: ($hosts | each { $in | extract-host }), + includes: $include_lines + } + } + + export def get-ssh-sites []: nothing -> table { + let files = [ + '/etc/ssh/ssh_config', + '~/.ssh/config' + ] | filter {|file| $file | path exists } + + + let first_result: record>, includes: list> = $files | par-each {|file| + let folder = $file | path expand | path dirname + let r = $file | open --raw | process + $r | update includes { each {|f| $folder | path join $f } } + } | reduce {|it| merge deep $it --strategy=append } + + let $includes: list = $first_result.includes | each {|f| + if '*' in $f { + glob $f + } else if ($f | path exists) { + [$f] + } else [] + } | flatten + + # Process include files + let included_hosts: list> = (if ($includes | is-empty) { [] } else { + let second_result = $includes | par-each {|p| $p | open --raw | process } | reduce {|it| merge deep $it --strategy=append } + $second_result.hosts + }) + + let hosts = $first_result.hosts ++ $included_hosts + $hosts | each {|h| [$"sftp://($h.name)" $"fish://($h.name)"] | wrap value | insert description $h.addr } | flatten + } + + export def get-bookmark-sites []: nothing -> table { + const FILE_PATH = '~/.local/share/lftp/bookmarks' | path expand + let sites = open --raw $FILE_PATH | lines | split column -n 2 -r \s+ value description + $sites | update value { $"bm:($in)" } + } +} + + +def "nu-complete lftp-site" [] { + use lftp-completion-utils get-ssh-sites + use lftp-completion-utils get-bookmark-sites + get-ssh-sites | append (get-bookmark-sites) +} + +export extern 'lftp' [ + -c: string # Execute the commands and exit + -d # Switch on debugging mode + -e: string # Execute the command just after selecting + -p: int # Use the port for connection + -u: string # Use the user/password for authentication + --norc # Don't execute rc files from the home directory + --help # Print the help and exit + --version # Print lftp version and exit + site?: string@"nu-complete lftp-site" # Host name, URL or bookmark name +]