Last active
April 6, 2024 04:44
-
-
Save Nejat/06c275749b7931857bb4c7604a026877 to your computer and use it in GitHub Desktop.
Lists and optionally upgrades available WinGet updates. Skips Unknown versions or anything in the skip list
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# winget-updates.nu | |
# GitHub Gists | |
# this script - https://gist.github.com/Nejat/06c275749b7931857bb4c7604a026877 | |
# example skip config - https://gist.github.com/Nejat/5a41fe09bb27c1bbcf345fb1dea8a234 | |
# powersheel version - https://gist.github.com/Nejat/6cb42f098320cb2fcb57a4e1728ca29e | |
# output formatting constants | |
const err_color: string = 'light_red' | |
const fail_color: string = 'red' | |
const hdr_color: string = 'light_blue' | |
const msg_color: string = 'light_yellow' | |
const new_color: string = 'cyan' | |
const ok_color: string = 'light_green' | |
const skp_color: string = 'yellow' | |
const upg_color: string = 'magenta' | |
# column format padding | |
const extra_pad: int = 2 | |
# status indicators | |
const fail_msg: string = 'Upgrad Failed' | |
const new_msg: string = 'New Version Available' | |
const ok_msg: string = 'Successfully Upgraded' | |
const skp_msg: string = 'Skipped' | |
const upg_msg: string = 'Installing Upgrade ...' | |
# random nothing messages | |
# - have any more witty/whimsical/nonsensical/useless suggestions? | |
const nada: list<string> = [ | |
'Nothing to upgrade', | |
'¡Nada Zilch Nichts!', | |
'🤙 Nah Brah', | |
'Goose Egg 🥚', | |
'Zero found', | |
'Big fat NOTHING!' | |
'Nothing to see here, keep it moving ...', | |
'¯\_(ツ)_/¯', | |
] | |
# column names for a table of available upgrades | |
const columns: list<string> = [name version available status] | |
# upgrade table's column alignments | |
const alignment: record = { | |
name: left, | |
id: left, | |
version: right, | |
available: right, | |
status: left, | |
} | |
# minimum widths for output columns | |
let width_minimums: table = [ | |
{ | |
status: ([$upg_msg $fail_msg $ok_msg] | get-max-width) | |
} | |
] | |
# list all packages that have new versions available | |
def "main list" [ | |
--skip_file (-s): string = 'wngt-cfg' # skip configuration file | |
]: string -> nothing { | |
process-upgrade --skip_file $skip_file | |
} | |
# upgrade all packages that have new versions available | |
def "main upgrade" [ | |
--skip_file (-s): string = 'wngt-cfg' # skip configuration file | |
]: string -> nothing { | |
process-upgrade --skip_file $skip_file --upgrade | |
} | |
# top level help | |
def main []: nothing -> nothing { | |
nu winget-updates.nu --help | |
} | |
# primary script logic | |
def process-upgrade [ | |
# skip file is used to skip upgrades that winget fails to handle properlyy | |
--skip_file (-s): string, | |
# flag to process the upgrades, otherwise it will only list packages with new versions available | |
--upgrade (-u) | |
] -> nothing { | |
# cast to upgrade flag to boolean | |
let upgrade: bool = ($upgrade | into bool) | |
# get a table of available upgrades | |
let upgrades: table = (get-updates $skip_file) | |
# get a record of max column widths for all columns of the upgrades table | |
let col_widths: record = (get-column-widths $upgrades $width_minimums) | |
# determine the formatting joiners | |
let hdr_join: string = if $upgrade { '─┴─' } else { '─┼─' } | |
let fld_join: string = if $upgrade { ' ' } else { ' │ ' } | |
print '' | |
# output table header | |
let header: string = ( | |
$columns | |
| each { | |
|col| ($col | str capitalize) | fill -a ($alignment | get $col) -c ' ' -w ($col_widths | get $col) | |
} | |
| str join $'(ansi reset) │ (ansi $hdr_color)' | |
) | |
print $"(ansi $hdr_color)($header)(ansi reset)" | |
# output header bottom border | |
print ( | |
$columns | |
| each { | |
|col| '' | fill -a ($alignment | get $col) -c '─' -w ($col_widths | get $col) | |
} | str join $hdr_join | |
) | |
# process/list all upgrades | |
for up: record in $upgrades { | |
# initialize a list of padded columns for output | |
let base_record: list<string> = [ | |
($up.name | fill -a $alignment.name -c ' ' -w $col_widths.name) | |
($up.version | fill -a $alignment.version -c ' ' -w $col_widths.version) | |
($up.available | fill -a $alignment.available -c ' ' -w $col_widths.available) | |
] | |
let needs_upgrade: bool = $upgrade and ($up.status | str contains $new_msg) | |
# pad and append the existing status message if the package is not going to be upgraded | |
let record: list<string> = if not $needs_upgrade { | |
( | |
$base_record | |
| append ($up.status | fill -a $alignment.status -c ' ' -w $col_widths.status) | |
) | |
} else { | |
( | |
$base_record | |
| append ($'(ansi $upg_color)($upg_msg)(ansi reset)' | fill -a $alignment.status -c ' ' -w $col_widths.status) | |
) | |
} | |
# output the record, no carriage return pending upgrade status | |
let clear_msg: closure = (message-display $'($record | str join $fld_join)') | |
if not $upgrade { | |
# complete the new line | |
print '' | |
} else { | |
# requires upgrade if flagged for update and the package is not skipped | |
if $needs_upgrade { | |
# update package with winget | |
let result: record = (do { ^winget upgrade --id $up.id --source winget } | complete) | |
do $clear_msg | |
if $result.exit_code == 0 { | |
# complete record output with success message | |
let record: list<string> = ( | |
$base_record | |
| append ($'(ansi $ok_color)($ok_msg)(ansi reset)' | fill -a $alignment.status -c ' ' -w $col_widths.status) | |
) | |
print $'($record | str join $fld_join)' | |
} else { | |
# otherwise complete output with failure message plus any captured output(s) | |
let record: list<string> = ( | |
$base_record | |
| append ($'(ansi $err_color)($fail_msg)(ansi reset)' | fill -a $alignment.status -c ' ' -w $col_widths.status) | |
) | |
print $'($record | str join $fld_join)' | |
let has_fail_message: bool = false | |
# display captured stdout message if one exists | |
if not ($result.stdout | is-empty) { | |
print '' | |
print $'(ansi $msg_color)($result.stdout)(ansi reset)' | |
let has_fail_message: bool = true | |
} | |
# display captured stderr message if one exists | |
if not ($result.stderr | is-empty) { | |
print '' | |
print $'(ansi $fail_color)($result.stderr)(ansi reset)' | |
let has_fail_message: bool = true | |
} | |
# add trailing line space if additional messages were displayed | |
if $has_fail_message { print '' } | |
} | |
} else { | |
# otherwise, complete the new line | |
print '' | |
} | |
} | |
} | |
} | |
# determines the max length for each column in table | |
def get-column-widths [data: table, minimums: table] -> record { | |
$data | each { | |
|rec| $rec | into value | transpose key value | each { | |
|itm| { $itm.key: (([$itm.value $itm.key] | get-max-width ($minimums | get --ignore-errors $itm.key | default 0)) + $extra_pad) } | |
} | reduce --fold {} { |nxt, acc| {...$acc, ...$nxt } } | |
} | math max | |
} | |
# get max width of all strings in a list | |
def get-max-width [min_width: int = 0]: list<string> -> int { | |
$in | each { |itm| $itm | str length} | append $min_width | math max | |
} | |
# retrieve and parse available winget updates | |
def get-updates [skip_file: string] -> table { | |
let clear_msg: closure = (message-display $'Reading skip configuration file ($skip_file) ...') | |
let skipped: table = (read-skip-file $skip_file) | |
do $clear_msg | |
let clear_msg: closure = (message-display 'Searching for updates ...') | |
let upgrades: list<string> = ( | |
# query winget for any new versions available | |
winget upgrade --source winget --include-unknown | |
# convert output into lines, skipping empties | |
| lines --skip-empty | |
# skip any extra heading lines, start at column headers | |
| skip until { |ln| $ln =~ 'Name.*Id.*Version.*Available' } | |
# only include header and data lines, skip header border and trailing message | |
| where not ($it =~ 'upgrades available|-----') | |
) | |
do $clear_msg | |
# check if there is anything to upgrade | |
if ($upgrades | is-empty) or ($upgrades | any { |ln| $ln | str contains 'No installed package found' }) { | |
# exit expressively if nothing available to upgrade | |
print $"\n($nada | get (random int ..(($nada | length) - 1)))\n" | |
exit 0 | |
} | |
# get the first record, which should be the titlebar | |
let title_bar: record = ($upgrades | get 0) | |
# find the indecies of the columns for parsing | |
let nm_idx: int = ($title_bar | str index-of 'Name') | |
let id_idx: int = (($title_bar | str index-of 'Id') - $nm_idx) | |
let vr_idx: int = (($title_bar | str index-of 'Version') - $nm_idx) | |
let av_idx: int = (($title_bar | str index-of 'Available') - $nm_idx) | |
# parse the winget output, skipping the title line | |
($upgrades | skip 1) | each { |up| | |
{ | |
name: ($up | str substring ..$id_idx | str trim), | |
id: ($up | str substring $id_idx..$vr_idx | str trim), | |
version: ($up | str substring $vr_idx..$av_idx | str trim), | |
available: ($up | str substring $av_idx.. | str trim), | |
# deetermine if the upgrade is configured to skip | |
# if skipped; uses the comments of the skip configuration for a status | |
# othewrwise; uses the new message for a status | |
status: ( | |
$skipped | where { |skp| $up | str contains $skp.skip } | |
| append [[skip comments]; ['' $'(ansi $new_color)($new_msg)(ansi reset)']] | |
| take 1 | |
).0.comments | |
} | |
} | |
} | |
# erases text on the current line for width of message | |
def message-clear [msg: string] { | |
if ($msg| str length) > 0 { | |
print -n $"\r('' | fill -w ($msg | str length) -c ' ')\r" | |
} | |
} | |
# displays a message with no new line, returns a clousre to clear the message displayed | |
def message-display [msg: string] -> closure { | |
print -n $"($msg)\r" | |
{ || message-clear $msg } | |
} | |
# read and parse skip configuration file | |
def read-skip-file [file: string] -> table { | |
if not ($file | path exists) { return [] } | |
( | |
open $file | |
| lines --skip-empty | |
| split column '#' skip comments | |
| str trim | |
| each { |rw| | |
{ | |
skip: $rw.skip, | |
comments: ( | |
if (($rw | columns | length) > 1) { | |
$'(ansi $skp_color)($skp_msg)(ansi reset); ($rw.comments)' | |
} else { | |
'' | |
} | |
) | |
} | |
} | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment