ohmyzsh/plugins/iw/_iw
Jeremy Melanson 8c079e8160 feat!: Add support for "iw" command
- CLI completion for iw (Linux wireless configuration utility).
- Directly parses "iw help" output to discover commands and caches the result.
- $service is used as the iw binary name. This allows this completion to work
  for differently-named iw binaries: compdef _iw my-iw-binary
2026-06-07 15:19:04 -04:00

220 lines
6.2 KiB
Text

#compdef iw
#
# ZSH completion for iw (Linux wireless configuration utility).
# Parses 'iw help' output to discover commands and caches the result.
#
# $service is used as the iw binary name. This allows this completion to work
# for differently-named iw binaries: compdef _iw my-iw-binary
# Parse 'iw help' output and write a sourceable cache file.
_iw_build_cache() {
local iw_cmd="$1"
local version="$2"
local cache_file="$3"
local help_output
help_output=$($iw_cmd help 2>&1) || return 1
local -a top_cmds dev_subcmds phy_subcmds wdev_subcmds reg_subcmds
local -A dev_subs phy_subs wdev_subs
local line
local -a words lines
# Single pass: collect top-level commands, subcmds, and sub-subcommands.
# words[4] entries starting with '<', '[', or '-' are placeholders, not completions.
lines=(${(f)help_output})
for line in $lines; do
words=(${=line})
[[ $line == $'\t'[a-z]* && $line != $'\t\t'* ]] && top_cmds+=($words[1])
case $words[1] in
dev)
if [[ $words[2] == '<devname>' && $words[3] == [a-z]* ]]; then
dev_subcmds+=($words[3])
if (( ${#words} > 3 )); then
case $words[4] in
'<'*|'['*|'-'*) ;;
*) dev_subs[$words[3]]+=" $words[4]" ;;
esac
fi
fi
;;
phy)
if [[ $words[2] == '<phyname>' && $words[3] == [a-z]* ]]; then
phy_subcmds+=($words[3])
if (( ${#words} > 3 )); then
case $words[4] in
'<'*|'['*|'-'*) ;;
*) phy_subs[$words[3]]+=" $words[4]" ;;
esac
fi
fi
;;
wdev)
if [[ $words[2] == '<idx>' && $words[3] == [a-z]* ]]; then
wdev_subcmds+=($words[3])
if (( ${#words} > 3 )); then
case $words[4] in
'<'*|'['*|'-'*) ;;
*) wdev_subs[$words[3]]+=" $words[4]" ;;
esac
fi
fi
;;
reg)
[[ $words[2] == [a-z]* ]] && reg_subcmds+=($words[2])
;;
esac
done
# (ou): sorted + unique, replacing sort -u
top_cmds=(${(ou)top_cmds})
dev_subcmds=(${(ou)dev_subcmds})
phy_subcmds=(${(ou)phy_subcmds})
wdev_subcmds=(${(ou)wdev_subcmds})
reg_subcmds=(${(ou)reg_subcmds})
local subcmd subs_str
local -a subs_arr tmp_arr
{
print "# iw completion cache"
print "# version: ${version}"
print "_iw_top_cmds=(${top_cmds[*]})"
print "_iw_dev_subcmds=(${dev_subcmds[*]})"
for subcmd in $dev_subcmds; do
if [[ -n "${dev_subs[$subcmd]}" ]]; then
tmp_arr=(${=dev_subs[$subcmd]})
subs_arr=(${(ou)tmp_arr})
subs_str="${(j: :)subs_arr}"
print "_iw_dev_subsubcmds[${subcmd}]=${(qq)subs_str}"
fi
done
print "_iw_phy_subcmds=(${phy_subcmds[*]})"
for subcmd in $phy_subcmds; do
if [[ -n "${phy_subs[$subcmd]}" ]]; then
tmp_arr=(${=phy_subs[$subcmd]})
subs_arr=(${(ou)tmp_arr})
subs_str="${(j: :)subs_arr}"
print "_iw_phy_subsubcmds[${subcmd}]=${(qq)subs_str}"
fi
done
print "_iw_wdev_subcmds=(${wdev_subcmds[*]})"
for subcmd in $wdev_subcmds; do
if [[ -n "${wdev_subs[$subcmd]}" ]]; then
tmp_arr=(${=wdev_subs[$subcmd]})
subs_arr=(${(ou)tmp_arr})
subs_str="${(j: :)subs_arr}"
print "_iw_wdev_subsubcmds[${subcmd}]=${(qq)subs_str}"
fi
done
print "_iw_reg_subcmds=(${reg_subcmds[*]})"
} >| "$cache_file"
}
_iw() {
local context state line curcontext="$curcontext"
integer ret=1
# Declare cache variables; populated by sourcing the cache file below.
local -a _iw_top_cmds _iw_dev_subcmds _iw_phy_subcmds _iw_wdev_subcmds _iw_reg_subcmds
local -A _iw_dev_subsubcmds _iw_phy_subsubcmds _iw_wdev_subsubcmds
local cache_file="${ZSH_CACHE_DIR:-${HOME}/.cache}/_iw_cache"
local current_version ver_output
ver_output=$(${service} --version 2>&1)
[[ $ver_output =~ '([0-9]+\.[0-9]+)' ]] && current_version=$match[1] || current_version=""
local cache_ver="" _l1 _l2
if [[ -f "$cache_file" ]]; then
{ IFS= read -r _l1; IFS= read -r _l2; } < "$cache_file" 2>/dev/null
cache_ver=${_l2#\# version: }
fi
if [[ $cache_ver != $current_version ]]; then
_iw_build_cache "${service}" "$current_version" "$cache_file"
fi
source "$cache_file" 2>/dev/null
_arguments -C \
'--debug[enable netlink debugging]' \
'(- 1 2 3 4)--version[show version]' \
'1:: :->cmd' \
'2:: :->arg2' \
'3:: :->arg3' \
'4:: :->arg4' \
&& return 0
case "$state" in
cmd)
local -A _iw_cmd_descs
_iw_cmd_descs=(
dev 'network interface commands'
phy 'wireless hardware commands'
wdev 'wireless device commands'
reg 'regulatory domain commands'
list 'list all wireless devices and their capabilities'
event 'monitor kernel events'
features 'list supported features'
commands 'list all known commands'
help 'show command usage'
)
local -a top_cmds
local _cmd
for _cmd in $_iw_top_cmds; do
if [[ -n "${_iw_cmd_descs[$_cmd]}" ]]; then
top_cmds+=("${_cmd}:${_iw_cmd_descs[$_cmd]}")
else
top_cmds+=("$_cmd")
fi
done
_describe -t commands 'iw command' top_cmds && ret=0
;;
arg2)
case "$line[1]" in
dev)
local -a wlan_devs
wlan_devs=(/sys/class/net/*/phy80211(N:h:t))
compadd -a wlan_devs && ret=0
;;
phy)
local -a phy_devs
phy_devs=(/sys/class/ieee80211/*(N:t))
compadd -a phy_devs && ret=0
;;
reg)
compadd -a _iw_reg_subcmds && ret=0
;;
esac
;;
arg3)
case "$line[1]" in
dev) compadd -a _iw_dev_subcmds && ret=0 ;;
phy) compadd -a _iw_phy_subcmds && ret=0 ;;
wdev) compadd -a _iw_wdev_subcmds && ret=0 ;;
esac
;;
arg4)
local -a subs
case "$line[1]" in
dev) subs=(${=_iw_dev_subsubcmds[$line[3]]}) ;;
phy) subs=(${=_iw_phy_subsubcmds[$line[3]]}) ;;
wdev) subs=(${=_iw_wdev_subsubcmds[$line[3]]}) ;;
esac
(( ${#subs} > 0 )) && compadd -a subs && ret=0
;;
esac
return ret
}
_iw "$@"