Skip to content

Commit

Permalink
Merge pull request #1069 from akinomyoga/use-awk-for-make
Browse files Browse the repository at this point in the history
refactor(make): Use awk to extract target names
  • Loading branch information
akinomyoga committed Nov 27, 2023
2 parents 15f3ecc + 302dc52 commit 63dbf2b
Show file tree
Hide file tree
Showing 11 changed files with 137 additions and 98 deletions.
16 changes: 16 additions & 0 deletions bash_completion
Original file line number Diff line number Diff line change
Expand Up @@ -3212,6 +3212,22 @@ _comp_xfunc()
"$xfunc_name" "${@:3}"
}
# Call a POSIX-compatible awk. Solaris awk is not POSIX-compliant, but Solaris
# provides a POSIX-compatible version through /usr/xpg4/bin/awk. We switch the
# implementation to /usr/xpg4/bin/awk in Solaris if any.
# @since 2.12
if [[ $OSTYPE == *solaris* && -x /usr/xpg4/bin/awk ]]; then
_comp_awk()
{
/usr/xpg4/bin/awk "$@"
}
else
_comp_awk()
{
command awk "$@"
}
fi
# source compat completion directory definitions
_comp__init_compat_dirs=()
if [[ ${BASH_COMPLETION_COMPAT_DIR-} ]]; then
Expand Down
8 changes: 4 additions & 4 deletions completions/_nmcli
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,25 @@
_comp_cmd_nmcli__con_id()
{
_comp_compgen_split -l -- "$(nmcli con list 2>/dev/null |
tail -n +2 | awk -F ' {2,}' '{print $1 }')"
tail -n +2 | _comp_awk -F ' {2,}' '{print $1}')"
}

_comp_cmd_nmcli__con_uuid()
{
_comp_compgen_split -- "$(nmcli con list 2>/dev/null |
tail -n +2 | awk -F ' {2,}' '{print $2}')"
tail -n +2 | _comp_awk -F ' {2,}' '{print $2}')"
}

_comp_cmd_nmcli__ap_ssid()
{
_comp_compgen_split -l -- "$(nmcli dev wifi list 2>/dev/null |
tail -n +2 | awk -F ' {2,}' '{print $1}')"
tail -n +2 | _comp_awk -F ' {2,}' '{print $1}')"
}

_comp_cmd_nmcli__ap_bssid()
{
_comp_compgen_split -- "$(nmcli dev wifi list 2>/dev/null |
tail -n +2 | awk -F ' {2,}' '{print $2}')"
tail -n +2 | _comp_awk -F ' {2,}' '{print $2}')"
}

_comp_cmd_nmcli()
Expand Down
2 changes: 1 addition & 1 deletion completions/convert
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ _comp_cmd_convert__common_options()
return
;;
-format)
_comp_compgen_split -- "$(convert -list format | awk \
_comp_compgen_split -- "$(convert -list format | _comp_awk \
'/ [r-][w-][+-] / { sub("[*]$","",$1); print tolower($1) }')"
return
;;
Expand Down
4 changes: 2 additions & 2 deletions completions/dpkg
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ _comp_xfunc_dpkg_compgen_installed_packages()
grep-status -P -e "^${cur-}" -a \
-FStatus 'ok installed' \
-n -s Package 2>/dev/null ||
command awk -F '\n' -v RS="" "
_comp_awk -F '\n' -v RS="" "
index(\$1, \"Package: ${cur-}\") == 1 &&
\$2 ~ /ok installed|half-installed|unpacked|half-configured|^Essential: yes/ {
print(substr(\$1, 10));
Expand All @@ -22,7 +22,7 @@ _comp_xfunc_dpkg_compgen_purgeable_packages()
grep-status -P -e "^${cur-}" -a \
-FStatus 'ok installed' -o -FStatus 'ok config-files' \
-n -s Package 2>/dev/null ||
command awk -F '\n' -v RS="" "
_comp_awk -F '\n' -v RS="" "
index(\$1, \"Package: ${cur-}\") == 1 &&
\$2 ~ /ok installed|half-installed|unpacked|half-configured|config-files|^Essential: yes/ {
print(substr(\$1, 10));
Expand Down
4 changes: 2 additions & 2 deletions completions/iwconfig
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ _comp_cmd_iwconfig()
_comp_compgen -- -W 'on off any'
if [[ ${BASH_COMPLETION_CMD_IWCONFIG_SCAN-${COMP_IWLIST_SCAN-}} ]]; then
_comp_compgen -a split -- "$(iwlist "${words[1]}" scan |
awk -F'\"' '/ESSID/ {print $2}')"
_comp_awk -F '\"' '/ESSID/ {print $2}')"
fi
return
;;
Expand All @@ -38,7 +38,7 @@ _comp_cmd_iwconfig()
_comp_compgen -- -W 'on off any'
if [[ ${BASH_COMPLETION_CMD_IWCONFIG_SCAN-${COMP_IWLIST_SCAN-}} ]]; then
_comp_compgen -a split -- "$(iwlist "${words[1]}" scan |
awk -F ': ' '/Address/ {print $2}')"
_comp_awk -F ': ' '/Address/ {print $2}')"
fi
return
;;
Expand Down
100 changes: 15 additions & 85 deletions completions/make
Original file line number Diff line number Diff line change
@@ -1,91 +1,21 @@
# bash completion for GNU make -*- shell-script -*-

# TODO: rename per API conventions, rework to use vars rather than outputting(?)
_make_target_extract_script()
# Extract the valid target names starting with PREFIX from the output of
# `make -npq'
# @param mode If this is `-d', the directory names already specified in
# PREFIX are omitted in the output
# @param prefix Prefix of the target names
_comp_cmd_make__extract_targets()
{
local mode="$1"
shift

local prefix="$1"
local prefix_pat=$(command sed 's/[][\,.*^$(){}?+|/]/\\&/g' <<<"$prefix")
local basename=${prefix##*/}
local dirname_len=$((${#prefix} - ${#basename}))
# Avoid expressions like '\(.\{0\}\)', FreeBSD sed doesn't like them:
# > sed: 1: ...: RE error: empty (sub)expression
local dirname_re=
((dirname_len > 0)) && dirname_re="\(.\{${dirname_len}\}\)"

if [[ ! $dirname_re ]]; then
local output="\1"
elif [[ $mode == -d ]]; then
# display mode, only output current path component to the next slash
local output="\2"
else
# completion mode, output full path to the next slash
local output="\1\2"
fi

cat <<EOF
1,/^# * Make data base/ d; # skip any makefile output
/^# * Finished Make data base/,/^# * Make data base/{
d; # skip any makefile output
}
/^# * Variables/,/^# * Files/ d; # skip until files section
/^# * Not a target/,/^$/ d; # skip not target blocks
/^${prefix_pat}/,/^$/! d; # skip anything user dont want
# The stuff above here describes lines that are not
# explicit targets or not targets other than special ones
# The stuff below here decides whether an explicit target
# should be output.
/^# * File is an intermediate prerequisite/ {
s/^.*$//;x; # unhold target
d; # delete line
}
/^$/ { # end of target block
x; # unhold target
/^$/d; # dont print blanks
s|^${dirname_re-}\(.\{${#basename}\}[^:]*\):.*$|${output}|p;
d; # hide any bugs
}
# This pattern includes a literal tab character as \t is not a portable
# representation and fails with BSD sed
/^[^# :%]\{1,\}:/ { # found target block
/^\.PHONY:/ d; # special target
/^\.SUFFIXES:/ d; # special target
/^\.DEFAULT:/ d; # special target
/^\.PRECIOUS:/ d; # special target
/^\.INTERMEDIATE:/ d; # special target
/^\.SECONDARY:/ d; # special target
/^\.SECONDEXPANSION:/ d; # special target
/^\.DELETE_ON_ERROR:/ d; # special target
/^\.IGNORE:/ d; # special target
/^\.LOW_RESOLUTION_TIME:/ d; # special target
/^\.SILENT:/ d; # special target
/^\.EXPORT_ALL_VARIABLES:/ d; # special target
/^\.NOTPARALLEL:/ d; # special target
/^\.ONESHELL:/ d; # special target
/^\.POSIX:/ d; # special target
/^\.NOEXPORT:/ d; # special target
/^\.MAKE:/ d; # special target
EOF

# don't complete with hidden targets unless we are doing a partial completion
if [[ ! ${prefix_pat} || ${prefix_pat} == */ ]]; then
cat <<EOF
/^${prefix_pat}[^a-zA-Z0-9]/d; # convention for hidden tgt
EOF
fi
local mode=$1
local -x prefix=$2

cat <<EOF
h; # hold target
d; # delete line
}
# display mode, only output current path component to the next slash
local -x prefix_replace=$prefix
[[ $mode == -d && $prefix == */* ]] &&
prefix_replace=${prefix##*/}

EOF
_comp_awk -f "${BASH_SOURCE[0]%/*}/../helpers/make-extract-targets.awk"
}

# Truncate the non-unique filepaths in COMPREPLY to only generate unique
Expand Down Expand Up @@ -224,11 +154,11 @@ _comp_cmd_make()
# mode=-d # display-only mode
# fi

local IFS=$' \t\n' script=$(_make_target_extract_script $mode "$cur")
local IFS=$' \t\n'
COMPREPLY=($(LC_ALL=C \
$1 -npq __BASH_MAKE_COMPLETION__=1 \
${makef+"${makef[@]}"} "${makef_dir[@]}" .DEFAULT 2>/dev/null |
command sed -ne "$script"))
_comp_cmd_make__extract_targets "$mode" "$cur"))

_comp_cmd_make__truncate_non_unique_paths

Expand Down
2 changes: 1 addition & 1 deletion completions/perl
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ _comp_cmd_perldoc()
if [[ $cur == p* ]]; then
_comp_compgen -a split -- "$(PERLDOC_PAGER=cat "$1" -u perl |
command sed -ne '/perl.*Perl overview/,/perlwin32/p' |
awk '$NF=2 && $1 ~ /^perl/ { print $1 }')"
awk 'NF >= 2 && $1 ~ /^perl/ { print $1 }')"
fi
fi
_comp_compgen -a filedir 'p@([lm]|od)'
Expand Down
2 changes: 1 addition & 1 deletion completions/portinstall
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ _comp_cmd_portinstall()
indexfile=$portsdir/INDEX
[[ -f $indexfile && -r $indexfile ]] || return

_comp_compgen_split -l -- "$(awk -F '|' '
_comp_compgen_split -l -- "$(_comp_awk -F '|' '
BEGIN { portsdir = ENVIRON["portsdir"]; len = length(portsdir) }
{ print $1 }
substr($2, 1, len) == portsdir { print substr($2, len + 1) }
Expand Down
2 changes: 1 addition & 1 deletion helpers/Makefile.am
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
helpersdir = $(datadir)/$(PACKAGE)/helpers
helpers_DATA = perl python
helpers_DATA = perl python make-extract-targets.awk

EXTRA_DIST = $(helpers_DATA)
93 changes: 93 additions & 0 deletions helpers/make-extract-targets.awk
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# helper AWK script for GNU make -*- awk -*-

# This AWK script is used by the function `_comp_cmd_make__extract_targets` in
# `completions/make`. This script receives the output of `make -npq' as the
# input file or stdin and outputs the list of targets matching the prefix.
#
# @env prefix Specifies the prefix to match.
# @env prefix_replace Specifies the string that replaces the prefix in the
# output. This is used when we want to omit the directory name in showing
# the list of the completions.
#

BEGIN {
prefix = ENVIRON["prefix"];
prefix_replace = ENVIRON["prefix_replace"];
is_target_block = 0;
target = "";
}

function starts_with(str, prefix) {
return substr(str, 1, length(prefix)) == prefix;
}

# skip any makefile outputs
NR == 1, /^# +Make data base/ { next; }
/^# +Finished Make data base/,/^# +Make data base/ { next; }

# skip until files section
/^# +Variables/, /^# +Files/ { next; }

# skip not-target blocks
/^# +Not a target/, /^$/ { next; }

# The stuff above here describes lines that are not
# explicit targets or not targets other than special ones
# The stuff below here decides whether an explicit target
# should be output.

# only process the targets the user wants.
starts_with($0, prefix) { is_target_block = 1; }
is_target_block == 0 { next; }

/^# +File is an intermediate prerequisite/ { # cancel the block
is_target_block = 0;
target = "";
next;
}

# end of target block
/^$/ {
is_target_block = 0;
if (target != "") {
print target;
target = "";
}
next;
}

# found target block
/^[^#\t:%]+:/ {
# special targets
if (/^\.PHONY:/ ) next;
if (/^\.SUFFIXES:/ ) next;
if (/^\.DEFAULT:/ ) next;
if (/^\.PRECIOUS:/ ) next;
if (/^\.INTERMEDIATE:/ ) next;
if (/^\.SECONDARY:/ ) next;
if (/^\.SECONDEXPANSION:/ ) next;
if (/^\.DELETE_ON_ERROR:/ ) next;
if (/^\.IGNORE:/ ) next;
if (/^\.LOW_RESOLUTION_TIME:/ ) next;
if (/^\.SILENT:/ ) next;
if (/^\.EXPORT_ALL_VARIABLES:/) next;
if (/^\.NOTPARALLEL:/ ) next;
if (/^\.ONESHELL:/ ) next;
if (/^\.POSIX:/ ) next;
if (/^\.NOEXPORT:/ ) next;
if (/^\.MAKE:/ ) next;

# dont complete with hidden targets unless we are doing a partial completion
if (prefix == "" || prefix ~ /\/$/)
if (substr($0, length(prefix) + 1, 1) ~ /[^a-zA-Z0-9]/)
next;

target = $0;
sub(/:.*/, "", target);
if (prefix_replace != prefix)
target = prefix_replace substr(target, 1 + length(prefix));

next;
}

# ex: filetype=awk
2 changes: 1 addition & 1 deletion test/runLint
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ filter_out=
gitgrep $cmdstart"awk\b.*-F([[:space:]]|[[:space:]]*[\"'][^\"']{2,})" \
'awk with -F char or -F ERE, use -Fchar instead (Solaris)'

gitgrep $cmdstart"awk\b.*\[:[a-z]*:\]" \
gitgrep $cmdstart"(_comp_)?awk\b.*\[:[a-z]*:\]" \
'awk with POSIX character class not supported in mawk (Debian/Ubuntu)'

gitgrep $cmdstart'sed\b.*\\[?+]' \
Expand Down

0 comments on commit 63dbf2b

Please sign in to comment.