Nushell, who dis?

This commit is contained in:
Jeffrey Serio 2024-02-18 22:52:08 -06:00
commit 782e6cd8a1
5 changed files with 778 additions and 0 deletions

196
config.nu Normal file
View File

@ -0,0 +1,196 @@
# Nushell Config File
#
# version = "0.90.1"
# Command existence checker function
def 'is-installed' [ app: string ] {
((which $app | length) > 0)
}
# Git aliases
export alias ga = git add
export alias gcl = git clone
export alias gcmsg = git commit -m
export alias gd = git diff
export alias gl = git pull
export alias gp = git push
export alias gr = git remote
export alias grbi = git rebase -i
export alias grm = git rm
export alias grv = git remote -v
export alias gst = git status
# Doom Emacs
def doomdoc [] { ~/.config/emacs/bin/doom doctor }
def dsync [] { ~/.config/emacs/bin/doom sync }
def dclean [] { ~/.config/emacs/bin/doom clean }
def dcomp [] { ~/.config/emacs/bin/doom compile }
def dpurge [] { ~/.config/emacs/bin/doom purge }
def denv [] { ~/.config/emacs/bin/doom env }
def dupgrade [] { ~/.config/emacs/bin/doom upgrade }
# firewalld
export alias fw = sudo firewall-cmd
export alias fwp = sudo firewall-cmd --permanent
export alias fwr = sudo firewall-cmd --reload
export alias fwrp = sudo firewall-cmd --runtime-to-permanent
# Execute bash on host (atomic desktop)
export alias hostexec = distrobox-host-exec bash
# Aliases for APT
export alias acs = sudo apt-cache search
export alias acp = sudo apt-cache policy
export alias afs = sudo apt-file search
export alias afu = sudo apt-file update
export alias aac = sudo apt autoclean
export alias agc = sudo apt clean
export alias agi = sudo apt install
export alias agli = sudo apt list --installed
export alias agp = sudo apt purge
export alias agr = sudo apt remove
export alias agu = sudo apt update
export alias agud = sudo apt update and sudo apt dist-upgrade
export alias agar = sudo apt autoremove
# Aliases for DNF
export alias dnfc = sudo dnf clean all
export alias dnfi = sudo dnf install
export alias dnfu = sudo dnf update
export alias dnfr = sudo dnf remove
export alias dnfs = sudo dnf search
# Copy SSH public key to clipboard
def pubkey [] { open --raw ~/.ssh/id_ed25519.pub | str trim | xclip -selection clipboard }
# Get public ip info
def pubip [] { curl --silent ipinfo.io | from json }
# Get Mullvad info
def amimulvad [] { curl -sSL https://am.i.mullvad/json | from json }
# Uptime info
def upt [] { uptime | jc --uptime | from json }
# df info
def dfj [] { df | jc --df | from json }
# /etc/fstab info
def etc-fstab [] { open --raw /etc/fstab | jc --fstab | from json }
# memory free info
def memfree [] { free | jc --free | from json }
# /etc/group
def etc-group [] { open --raw /etc/group | jc --group | from json }
# /etc/passwd
def etc-passwd [] { open --raw /etc/passwd | jc --passwd | from json }
# Network connections
def netcons [] { lsof -i | jc --lsof | from json }
# Ports
def tulp [] { ss -tulp | jc --ss | from json }
# Open ports
def openports [] { sudo lsof -i | jc --lsof | from json | find "LISTEN" }
# List sockets in use
def lsock [] { sudo lsof -i P | jc --lsof | from json }
# List UDP sockets in use
def lsocku [] { sudo lsof -nP | jc --lsof | from json | find "UDP" }
# List TCP sockets in use
def lsockt [] { sudo lsof -nP | jc --lsof | from json | find "TCP" }
# Print timestamp as %FT%T%:z
def tstampz [] { date now | format date "%FT%T%:z" }
# Create new directory and enter it
def --env mkd [path] { mkdir $path; cd $path }
# Display pid info of command
def pids [cmd] { ps | where name == "$cmd" }
# Get time in specific time zone
def whattimein [string] { date now | date to-timezone $string }
# cd to home and clear screen
def --env rsrc [] { cd $env.HOME; clear }
# Convert filename to given case
# Ex. caseify camel hello_world.txt
# => helloWorld.txt
# Based on https://github.com/nushell/nu_scripts/blob/main/sourced/cool-oneliners/file_convert_naming_case.nu
def caseify [case: string, filename: path] {
let ext = (echo $filename | path parse | get extension)
let cur_stem = (echo $filename | path parse | get stem)
let new_name = match $case {
"camel" => (((echo $cur_stem | str camel-case) + "." + $ext) | str join),
"kebab" => (((echo $cur_stem | str kebab-case) + "." + $ext) | str join),
"lower" => (((echo $cur_stem | str downcase) + "." + $ext) | str join),
"pascal" => (((echo $cur_stem | str pascal-case) + "." + $ext) | str join),
"screamsnake" => (((echo $cur_stem | str screaming-snake-case) + "." + $ext) | str join),
"snake" => (((echo $cur_stem | str snake-case) + "." + $ext) | str join),
_ => "Invalid case option"
}
mv $filename $new_name
}
# weather
use ~/.config/nushell/modules/get-weather.nu get_weather
export alias weather = get_weather
# ultimate-extractor
use ~/.config/nushell/modules/ultimate-extractor.nu extract
def x [path] { extract $path }
# ssh
use ~/.config/nushell/modules/ssh.nu
def nussh [] {
let ssh_host = (ssh ssh-list | get Host | input list $"(ansi yellow)Which host to SSH into?(ansi reset)")
if ($ssh_host | describe) != "string" {
echo "Operation cancelled"
return
}
ssh $ssh_host
}
def nulicense [] {
let licenses = (http get https://api.github.com/licenses)
let license_name = ($licenses | get name | input list $"(ansi yellow)Choose an open source license:(ansi reset)")
if ($license_name | describe) != "string" {
echo "Operation cancelled"
return
}
let license_body = (http get ($licenses | where name == $license_name).url.0).body
$license_body | save --raw -f LICENSE
}
def yaml2json [filename: path] {
let stem = (echo $filename | path parse | get stem)
open --raw $filename | from yaml | to json | save --raw ($stem + ".json" | str join)
}
$env.config = {
rm: {
always_trash: true
}
table: {
show_empty: false
}
keybindings: [
{
name: nussh
modifier: alt
keycode: char_s
mode: [emacs vi_normal vi_insert]
event: { send: executehostcommand cmd: nussh }
}
]
}

104
env.nu Normal file
View File

@ -0,0 +1,104 @@
# Nushell Environment Config File
#
# version = "0.90.1"
def create_left_prompt [] {
let home = $nu.home-path
# Perform tilde substitution on dir
# To determine if the prefix of the path matches the home dir, we split the current path into
# segments, and compare those with the segments of the home dir. In cases where the current dir
# is a parent of the home dir (e.g. `/home`, homedir is `/home/user`), this comparison will
# also evaluate to true. Inside the condition, we attempt to str replace `$home` with `~`.
# Inside the condition, either:
# 1. The home prefix will be replaced
# 2. The current dir is a parent of the home dir, so it will be uneffected by the str replace
let dir = (
if ($env.PWD | path split | zip ($home | path split) | all { $in.0 == $in.1 }) {
($env.PWD | str replace $home "~")
} else {
$env.PWD
}
)
let path_color = (if (is-admin) { ansi red_bold } else { ansi green_bold })
let separator_color = (if (is-admin) { ansi light_red_bold } else { ansi light_green_bold })
let path_segment = $"($path_color)($dir)"
$path_segment | str replace --all (char path_sep) $"($separator_color)(char path_sep)($path_color)"
}
def create_right_prompt [] {
# create a right prompt in magenta with green separators and am/pm underlined
let time_segment = ([
(ansi reset)
(ansi magenta)
(date now | format date '[%H:%M:%S]') # try to respect user's locale
] | str join | str replace --regex --all "([/:])" $"(ansi green)${1}(ansi magenta)" |
str replace --regex --all "([AP]M)" $"(ansi magenta_underline)${1}")
let last_exit_code = if ($env.LAST_EXIT_CODE != 0) {([
(ansi rb)
($env.LAST_EXIT_CODE)
] | str join)
} else { "" }
([$last_exit_code, (char space), $time_segment] | str join)
}
def is-container [] {
($env.container? | describe) == "string"
}
def create_user_host [] {
let user_host = (ansi red) + (whoami) + (ansi reset) + '@' + (ansi blue) + (hostname) + (ansi reset)
let container_segment = (ansi green_bold) + "⬢ " + (ansi reset)
let user_host_segment = (if (is-container) { ($container_segment + $user_host) | str join } else { $user_host })
([$user_host_segment, ":", (create_left_prompt)] | str join)
}
# Use nushell functions to define your right and left prompt
$env.PROMPT_COMMAND = {|| create_user_host }
# FIXME: This default is not implemented in rust code as of 2023-09-08.
$env.PROMPT_COMMAND_RIGHT = {|| create_right_prompt }
# The prompt indicators are environmental variables that represent
# the state of the prompt
$env.PROMPT_INDICATOR = {|| "> " }
$env.PROMPT_INDICATOR_VI_INSERT = {|| ": " }
$env.PROMPT_INDICATOR_VI_NORMAL = {|| "> " }
$env.PROMPT_MULTILINE_INDICATOR = {|| "::: " }
# Specifies how environment variables are:
# - converted from a string to a value on Nushell startup (from_string)
# - converted from a value back to a string when running external commands (to_string)
# Note: The conversions happen *after* config.nu is loaded
$env.ENV_CONVERSIONS = {
"PATH": {
from_string: { |s| $s | split row (char esep) | path expand --no-symlink }
to_string: { |v| $v | path expand --no-symlink | str join (char esep) }
}
"Path": {
from_string: { |s| $s | split row (char esep) | path expand --no-symlink }
to_string: { |v| $v | path expand --no-symlink | str join (char esep) }
}
}
# Directories to search for scripts when calling source or use
# The default for this is $nu.default-config-dir/scripts
$env.NU_LIB_DIRS = [
($nu.default-config-dir | path join 'scripts') # add <nushell-config-dir>/scripts
]
# Directories to search for plugin binaries when calling register
# The default for this is $nu.default-config-dir/plugins
$env.NU_PLUGIN_DIRS = [
($nu.default-config-dir | path join 'plugins') # add <nushell-config-dir>/plugins
]
# To add entries to PATH (on Windows you might use Path), you can use the following pattern:
# $env.PATH = ($env.PATH | split row (char esep) | prepend '/some/path')
let gopath = ($env.HOME + '/go/bin' | str join)
$env.PATH = ($env.PATH | split row (char esep) | prepend $gopath)

315
modules/get-weather.nu Normal file
View File

@ -0,0 +1,315 @@
# From https://github.com/nushell/nu_scripts/blob/main/modules/weather/get-weather.nu
###################################################
## Weather Script based on IP Address v1.0
###################################################
def locations [] {
[
[location city_column state_column country_column lat_column lon_column];
["http://ip-api.com/json/" city region countryCode lat lon]
["https://ipapi.co/json/" city region_code country_code latitude longitude]
# ["https://freegeoip.app/json/" city region_code country_code latitude longitude] # doesn't appear to be free any longer
["https://ipwhois.app/json/" city region country_code latitude longitude]
]
}
def get_my_location [index: int] {
let loc_json = (http get (locations | select $index).0.location)
let city_column = (locations | select $index).0.city_column
let state_column = (locations | select $index).0.state_column
let country_column = (locations | select $index).0.country_column
let lat_column = (locations | select $index).0.lat_column
let lon_column = (locations | select $index).0.lon_column
# echo $loc_json
if ($city_column | str length) > 1 {
if ($state_column | str length) > 1 {
if ($country_column | str length) > 1 {
let lookup_state = ($loc_json | get ($state_column))
if ($lookup_state | str length) > 2 {
let state = (state_abbrev_lookup $lookup_state)
$"($loc_json | get ($city_column)),($state),($loc_json | get ($country_column))"
} else {
$"($loc_json | get ($city_column)),($loc_json | get ($state_column)),($loc_json | get ($country_column))"
}
} else {
$"($loc_json | get ($city_column)),($loc_json | get ($state_column))"
}
} else {
$"($loc_json | get ($city_column))"
}
} else {
"No City Found"
}
}
def get_location_by_ip [locIdx: int, token: string] {
let URL_QUERY_LOCATION = "https://api.openweathermap.org/geo/1.0/direct"
let location = (get_my_location $locIdx)
let url = $"($URL_QUERY_LOCATION)?q=($location)&limit=5&appid=($token)"
http get $url
}
def show-error [msg label err] {
let span = (metadata $err).span;
error make {msg: $msg, label: {text: $label, span: $span } }
}
def get_weather_by_ip [locIdx: int, units: string, token: string] {
# units
# f = imperial aka Fahrenheit
# c = metric aka Celcius
let URL_WEATHER = "https://api.openweathermap.org/data/2.5/weather"
let URL_FORECAST = "http://api.openweathermap.org/data/2.5/forecast/daily"
let coords = (get_location_by_ip $locIdx $token)
if ($coords | length) > 1 {
show-error "Error getting location" "There were more than one locations found" $coords
}
if $units == "f" {
let units = "imperial"
let url = $"($URL_WEATHER)?lat=($coords.lat.0)&lon=($coords.lon.0)&units=($units)&appid=($token)"
let url_forecast = $"($URL_FORECAST)?lat=($coords.lat.0)&lon=($coords.lon.0)&units=($units)&appid=($token)"
let weather = (http get $url)
let forecast_data = (http get $url_forecast)
let forecast = ($forecast_data.list | each {|day|
{
id: ($day.weather.0.id)
dt: ($day.dt * 1_000_000_000 | into string | into datetime -z local | format date '%a, %b %e') #'%Y-%m-%d')
high: ($day.temp.max)
low: ($day.temp.min)
}
})
let day1 = $"($forecast | get 0.dt) (get_emoji_by_id ($forecast | get 0.id | into string)) high: ($forecast | get 0.high | into string -d 1) low: ($forecast | get 0.low | into string -d 1)"
let day2 = $"($forecast | get 1.dt) (get_emoji_by_id ($forecast | get 1.id | into string)) high: ($forecast | get 1.high | into string -d 1) low: ($forecast | get 1.low | into string -d 1)"
let day3 = $"($forecast | get 2.dt) (get_emoji_by_id ($forecast | get 2.id | into string)) high: ($forecast | get 2.high | into string -d 1) low: ($forecast | get 2.low | into string -d 1)"
let day4 = $"($forecast | get 3.dt) (get_emoji_by_id ($forecast | get 3.id | into string)) high: ($forecast | get 3.high | into string -d 1) low: ($forecast | get 3.low | into string -d 1)"
let day5 = $"($forecast | get 4.dt) (get_emoji_by_id ($forecast | get 4.id | into string)) high: ($forecast | get 4.high | into string -d 1) low: ($forecast | get 4.low | into string -d 1)"
let day6 = $"($forecast | get 5.dt) (get_emoji_by_id ($forecast | get 5.id | into string)) high: ($forecast | get 5.high | into string -d 1) low: ($forecast | get 5.low | into string -d 1)"
let day7 = $"($forecast | get 6.dt) (get_emoji_by_id ($forecast | get 6.id | into string)) high: ($forecast | get 6.high | into string -d 1) low: ($forecast | get 6.low | into string -d 1)"
{
'Weather Location': $"($weather.name), ($weather.sys.country)"
Longitude: $weather.coord.lon
Latitude: $weather.coord.lat
Temperature: $"($weather.main.temp | into string -d 1) °F"
'Feels Like': $"($weather.main.feels_like | into string -d 1) °F"
Humidity: $weather.main.humidity
Pressure: $weather.main.pressure
Emoji: (get_icon_from_table $weather.weather.main.0)
'Forecast Day 1': $day1
'Forecast Day 2': $day2
'Forecast Day 3': $day3
'Forecast Day 4': $day4
'Forecast Day 5': $day5
'Forecast Day 6': $day6
'Forecast Day 7': $day7
}
} else {
let units = "metric"
let url = $"($URL_WEATHER)?lat=($coords.lat.0)&lon=($coords.lon.0)&units=($units)&appid=($token)"
let url_forecast = $"($URL_FORECAST)?lat=($coords.lat.0)&lon=($coords.lon.0)&units=($units)&appid=($token)"
let weather = (http get $url)
let forecast_data = (http get $url_forecast)
let forecast = ($forecast_data.list | each {|day|
{
id: ($day.weather.0.id)
dt: ($day.dt * 1_000_000_000 | into string | into datetime -z local | format date '%a, %b %e') #'%Y-%m-%d')
high: ($day.temp.max)
low: ($day.temp.min)
}
})
let day1 = $"($forecast | get 0.dt) (get_emoji_by_id ($forecast | get 0.id | into string)) high: ($forecast | get 0.high | into string -d 1) low: ($forecast | get 0.low | into string -d 1)"
let day2 = $"($forecast | get 1.dt) (get_emoji_by_id ($forecast | get 1.id | into string)) high: ($forecast | get 1.high | into string -d 1) low: ($forecast | get 1.low | into string -d 1)"
let day3 = $"($forecast | get 2.dt) (get_emoji_by_id ($forecast | get 2.id | into string)) high: ($forecast | get 2.high | into string -d 1) low: ($forecast | get 2.low | into string -d 1)"
let day4 = $"($forecast | get 3.dt) (get_emoji_by_id ($forecast | get 3.id | into string)) high: ($forecast | get 3.high | into string -d 1) low: ($forecast | get 3.low | into string -d 1)"
let day5 = $"($forecast | get 4.dt) (get_emoji_by_id ($forecast | get 4.id | into string)) high: ($forecast | get 4.high | into string -d 1) low: ($forecast | get 4.low | into string -d 1)"
let day6 = $"($forecast | get 5.dt) (get_emoji_by_id ($forecast | get 5.id | into string)) high: ($forecast | get 5.high | into string -d 1) low: ($forecast | get 5.low | into string -d 1)"
let day7 = $"($forecast | get 6.dt) (get_emoji_by_id ($forecast | get 6.id | into string)) high: ($forecast | get 6.high | into string -d 1) low: ($forecast | get 6.low | into string -d 1)"
{
'Weather Location': $"($weather.name), ($weather.sys.country)"
Longitude: $weather.coord.lon
Latitude: $weather.coord.lat
Temperature: $"($weather.main.temp | into string -d 1) °C"
'Feels Like': $"($weather.main.feels_like | into string -d 1) °C"
Humidity: $weather.main.humidity
Pressure: $weather.main.pressure
Emoji: (get_icon_from_table $weather.weather.main.0)
'Forecast Day 1': $day1
'Forecast Day 2': $day2
'Forecast Day 3': $day3
'Forecast Day 4': $day4
'Forecast Day 5': $day5
'Forecast Day 6': $day6
'Forecast Day 7': $day7
}
}
}
def weather_emoji_table [] {
{
Clear: (char sun)
Clouds: (char clouds)
Rain: (char rain)
Fog: (char fog)
Mist: (char mist)
Haze: (char haze)
Snow: (char snow)
Thunderstorm: (char thunderstorm)
}
}
def get_icon_from_table [w] {
weather_emoji_table | get $w
}
# Get the local weather by ip address
export def get_weather [
--locIdx(-l): int # The location id 0-2
--units(-u): string # The units "f" or "c"
] {
let token = "85a4e3c55b73909f42c6a23ec35b7147"
let is_loc_empty = ($locIdx == null)
let is_units_empty = ($units == null)
let no_loc_no_unit = ($is_loc_empty == true and $is_units_empty == true)
let no_loc_with_unit = ($is_loc_empty == true and $is_units_empty == false)
let with_loc_no_unit = ($is_loc_empty == false and $is_units_empty == true)
let with_loc_with_unit = ($is_loc_empty == false and $is_units_empty == false)
# This is a cautionary tale, the commented out code below is returning
# and autoview is viewing the data, so no structured data is being returned.
# The ramification to this is you can't do get_weather | select Temperature Emoji
# like you should be able to. The following uncommented section below fixes it.
# Hopefully we'll be able to fix this somehow because it's easy to fall into
# this hole without knowing.
# if $no_loc_no_unit {
# echo "no_loc_no_unit"
# (get_weather_by_ip 0 "f")
# } { }
# if $no_loc_with_unit {
# echo "no_loc_with_unit"
# (get_weather_by_ip 0 $units)
# } { }
# if $with_loc_no_unit {
# echo "with_loc_no_unit"
# (get_weather_by_ip $locIdx "f")
# } { }
# if $with_loc_with_unit {
# echo "with_loc_with_unit"
# (get_weather_by_ip $locIdx $units)
# } { }
if $no_loc_no_unit {
(get_weather_by_ip 0 "f" $token)
} else if $no_loc_with_unit {
(get_weather_by_ip 0 $units $token)
} else if $with_loc_no_unit {
(get_weather_by_ip $locIdx "f" $token)
} else if $with_loc_with_unit {
(get_weather_by_ip $locIdx $units $token)
}
}
def state_abbrev_lookup [state_name: string] {
# Weather Location 3 does not return state name abbreviations
# so we have to convert a state full name to a state abbreviation
let lookup_table = {
Alabama: AL
Alaska: AK
Arizona: AZ
Arkansas: AR
California: CA
Colorado: CO
Connecticut: CT
Delaware: DE
Florida: FL
Georgia: GA
Hawaii: HI
Idaho: ID
Illinois: IL
Indiana: IN
Iowa: IA
Kansas: KS
Kentucky: KY
Louisiana: LA
Maine: ME
Maryland: MD
Massachusetts: MA
Michigan: MI
Minnesota: MN
Mississippi: MS
Missouri: MO
Montana: MT
Nebraska: NE
Nevada: NV
'New Hampshire': NH
'New Jersey': NJ
'New Mexico': NM
'New York': NY
'North Carolina': NC
'North Dakota': ND
Ohio: OH
Oklahoma: OK
Oregon: OR
Pennsylvania: PA
'Rhode Island': RI
'South Carolina': SC
'South Dakota': SD
Tennessee: TN
Texas: TX
Utah: UT
Vermont: VT
Virginia: VA
Washington: WA
'West Virginia': WV
Wisconsin: WI
Wyoming: WY
}
$lookup_table | get $state_name
}
def get_emoji_by_id [id] {
let emoji_dict = ({
"200": "⚡", "201": "⚡", "202": "⚡", "210": "⚡", "211": "⚡", "212": "⚡", "221": "⚡", "230": "⚡",
"231": "⚡", "232": "⚡",
"300": "☔", "301": "☔", "302": "☔", "310": "☔", "311": "☔",
"312": "☔", "313": "☔", "314": "☔", "321": "☔",
"500": "☔", "501": "☔", "502": "☔", "503": "☔", "504": "☔",
"511": "☔", "520": "☔", "521": "☔", "522": "☔", "531": "☔",
"600": "❄️", "601": "❄️", "602": "❄️", "611": "❄️", "612": "❄️",
"613": "❄️", "615": "❄️", "616": "❄️", "620": "❄️", "621": "❄️",
"622": "❄️",
"701": "🌫️", "711": "🌫️", "721": "🌫️", "731": "🌫️", "741": "🌫️", "751": "🌫️", "761": "🌫️", "762": "🌫️",
"771": "🌫️",
"781": "🌀",
"800": "☀️",
"801": "🌤️", "802": "🌤️", "803": "☁️", "804": "☁️",
})
($emoji_dict | get $id)
}
# To run this call
# > get_weather
# it will default to location 0 and Fahrenheit degrees
# > get_weather -l 1
# This changes to location 1. Locations are listed in the locations custom command above
# > get_weather -l 2 -u c
# This uses location 2 and Celcius degrees. f = Fahrenheit, c = Celcius
# Since I live in the USA I have not tested outside the country.
# We'll take PRs for things that are broke or augmentations.
# HOW TO USE
# put this in your config.nu file
# use /path/to/get-weather.nu get_weather
#
# then from the nushell commmand prompt type
# get_weather

135
modules/ssh.nu Normal file
View File

@ -0,0 +1,135 @@
export def ensure-cache [cache paths action] {
mut cfgs = []
for i in $paths {
let cs = (do -i {ls $i})
if not ($cs | is-empty) {
$cfgs = ($cfgs | append $cs)
}
}
let cfgs = $cfgs
let ts = ($cfgs | sort-by modified | reverse | get 0.modified)
if ($ts | is-empty) { return false }
let tc = (do -i { ls $cache | get 0.modified })
if not (($cache | path exists) and ($ts < $tc)) {
mkdir ($cache | path dirname)
do $action | save -f $cache
}
open $cache
}
export def 'str max-length' [] {
$in | reduce -f 0 {|x, a|
if ($x|is-empty) { return $a }
let l = ($x | str length)
if $l > $a { $l } else { $a }
}
}
def "nu-complete ssh host" [] {
rg -LNI '^Host [a-z0-9_\-\.]+' ~/.ssh | lines | each {|x| $x | split row ' '| get 1}
}
export def parse-ssh-file [group] {
$in
| parse -r '(?P<k>Host|HostName|User|Port|IdentityFile)\s+(?P<v>.+)'
| append { k: Host, v: null}
| reduce -f { rst: [], item: {Host: null} } {|it, acc|
if $it.k == 'Host' {
$acc | upsert rst ($acc.rst | append $acc.item)
| upsert item { Host : $it.v, HostName: null, Port: null, User: null, IdentityFile: null, Group: $group }
} else {
$acc | upsert item ($acc.item | upsert $it.k $it.v)
}
}
| get rst
| where {|x| not (($x.Host | is-empty) or $x.Host =~ '\*')}
}
export def ssh-list [] {
rg -L -l 'Host' ~/.ssh
| lines
| each {|x| cat $x | parse-ssh-file $x}
| flatten
}
def fmt-group [p] {
$p | str replace $"($env.HOME)/.ssh/" ''
}
def "ssh-hosts" [] {
let cache = $'($env.HOME)/.cache/nu-complete/ssh.json'
ensure-cache $cache [~/.ssh/config ~/.ssh/config*/* ] { ||
let data = (ssh-list | each {|x|
let uri = $"($x.User)@($x.HostName):($x.Port)"
{
value: $x.Host,
uri: $uri,
group: $"(fmt-group $x.Group)",
identfile: $"($x.IdentityFile)",
}
})
let max = {
value: ($data.value | str max-length),
uri: ($data.uri | str max-length),
group: ($data.group | str max-length),
identfile: ($data.identfile | str max-length),
}
{max: $max, completion: $data}
}
}
def "nu-complete ssh" [] {
let data = (ssh-hosts)
$data.completion
| each { |x|
let uri = ($x.uri | fill -a l -w $data.max.uri -c ' ')
let group = ($x.group | fill -a l -w $data.max.group -c ' ')
let id = ($x.identfile | fill -a l -w $data.max.identfile -c ' ')
{value: $x.value, description: $"\t($uri) ($group) ($id)" }
}
}
export extern main [
host: string@"nu-complete ssh" # host
...cmd # cmd
-v # verbose
-i: string # key
-p: int # port
-N # n
-T # t
-L # l
-R # r
-D # d
-J: string # j
-W: string # w
]
def "nu-complete scp" [cmd: string, offset: int] {
let argv = ($cmd | str substring ..$offset | split row ' ')
let p = if ($argv | length) > 2 { $argv | get 2 } else { $argv | get 1 }
let ssh = (ssh-hosts | get completion
| each {|x| {value: $"($x.value):" description: $x.uri} }
)
let n = ($p | split row ':')
if $"($n | get 0):" in ($ssh | get value) {
^ssh ($n | get 0) $"sh -c 'ls -dp ($n | get 1)*'"
| lines
| each {|x| $"($n | get 0):($x)"}
} else {
let files = (do -i {
ls -a $"($p)*"
| each {|x| if $x.type == dir { $"($x.name)/"} else { $x.name }}
})
$files | append $ssh
}
}
export def scp [
lhs: string@"nu-complete scp",
rhs: string@"nu-complete scp"
] {
^scp -r $lhs $rhs
}

View File

@ -0,0 +1,28 @@
# From https://github.com/nushell/nu_scripts/blob/main/modules/data_extraction/ultimate_extractor.nu
# Function to extract archives with different extensions.
export def extract [name:string] {
let handlers = [ [extension command];
['tar\.bz2|tbz|tbz2' 'tar xvjf']
['tar\.gz|tgz' 'tar xvzf']
['tar\.xz|txz' 'tar xvf']
['tar\.Z' 'tar xvZf']
['bz2' 'bunzip2']
['deb' 'ar x']
['gz' 'gunzip']
['pkg' 'pkgutil --expand']
['rar' 'unrar x']
['tar' 'tar xvf']
['xz' 'xz --decompress']
['zip|war|jar|nupkg' 'unzip']
['Z' 'uncompress']
['7z' '7za x']
]
let maybe_handler = ($handlers | where $name =~ $'\.(($it.extension))$')
if ($maybe_handler | is-empty) {
error make { msg: "unsupported file extension" }
} else {
let handler = ($maybe_handler | first)
nu -c ($handler.command + ' ' + $name)
}
}