-
-
Save konsolebox/ba31efab6609d200de3919197f1463b7 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
#!/bin/bash -p | |
# ph (Portage Helper) | |
# | |
# An opinionated tool that provides useful commands for querying and | |
# modifying Portage-related stuff | |
# | |
# Usage: ph command [arguments] | |
# | |
# Copyright (c) 2024 konsolebox | |
# | |
# Permission is hereby granted, free of charge, to any person obtaining | |
# a copy of this software and associated documentation files (the | |
# “Software”), to deal in the Software without restriction, including | |
# without limitation the rights to use, copy, modify, merge, publish, | |
# distribute, sublicense, and/or sell copies of the Software, and to | |
# permit persons to whom the Software is furnished to do so, subject to | |
# the following conditions: | |
# | |
# The above copyright notice and this permission notice shall be | |
# included in all copies or substantial portions of the Software. | |
# | |
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, | |
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
[ -n "${BASH_VERSION-}" ] && [[ BASH_VERSINFO -ge 5 ]] || { | |
echo "This scripts requires Bash version 5.0 or newer." >&2 | |
exit 1 | |
} | |
set -fu -o pipefail +o posix && shopt -s assoc_expand_once expand_aliases extglob nullglob || exit | |
_PH_WORLD_FILE=/var/lib/portage/world | |
function ph.common.err { | |
printf '%s\n' "$1" >&2 | |
} | |
alias err=ph.common.err | |
function ph.common.die { | |
ph.common.err "$@" | |
exit 1 | |
} | |
alias die=ph.common.die | |
function ph.common.print_mnemonic { | |
local msg=$1 fmsg u=$'\e[4m' r=$'\e[0m' c i j n t | |
for (( i = 0, j = ${#msg}; i < j; ++i )); do | |
c=${msg:i:1} n=${msg:i + 1:1} | |
if [[ $c == _ && $n ]]; then | |
if [[ $n == _ ]]; then | |
fmsg+=_ | |
else | |
fmsg+=${u}${n}${r} | |
fi | |
(( ++i )) | |
else | |
fmsg+=$c | |
fi | |
done | |
printf '%s\n' "${fmsg}" | |
} | |
alias print_mnemonic=ph.common.print_mnemonic | |
function ph.common.get_opt_and_optarg { | |
local optional=false | |
if [[ $1 == @optional ]]; then | |
optional=true | |
shift | |
fi | |
OPT=$1 OPTARG= OPTSHIFT=0 | |
if [[ $1 == -[!-]?* ]]; then | |
OPT=${1:0:2} OPTARG=${1:2} | |
elif [[ $1 == --*=* ]]; then | |
OPT=${1%%=*} OPTARG=${1#*=} | |
elif [[ ${2+.} && (${optional} == false || $2 != -?*) ]]; then | |
OPTARG=$2 OPTSHIFT=1 | |
elif [[ ${optional} == true ]]; then | |
return 1 | |
else | |
die "The '$1' option requires an argument." 2 | |
fi | |
return 0 | |
} | |
alias get_opt_and_optarg=ph.common.get_opt_and_optarg | |
function ph.common.check_external_command { | |
local __ | |
for __; do | |
# The type command searches a hash table first so don't bother using a cache. | |
type -P "$__" >/dev/null || die "Required external command not found: $__" | |
done | |
} | |
function ph.common.check_package_name_internal { | |
local pkg=$1 parsed | |
ph.common.check_external_command qatom | |
[[ ${pkg} == +([!/])/+([!/]) ]] || return | |
parsed=$(qatom -F '%{pfx}%{C}/%{PF}:%{SLOT}/%{SUBSLOT}::%{REPO}' -- "${pkg}") || return | |
[[ ${parsed} ]] || return | |
parsed=${parsed%::<unset>} parsed=${parsed#<unset>} | |
[[ ${parsed%%:*} == +([!/])/+([!/]) ]] || return | |
[[ ${parsed} != *::* || ${parsed} == *::+([[:alnum:]_-]) ]] || return | |
parsed=${parsed//?<unset>} | |
[[ "${pkg}" == ${parsed} ]] | |
} | |
function ph.common.check_package_name { | |
ph.common.check_package_name_internal "$1" || die "Invalid or incomplete package name: $1" 2 | |
} | |
function ph.common.get_filename_equivalent { | |
local __pkg=$1 | |
local -n ___filename=$2 | |
___filename=${pkg/\//.} ___filename=${___filename//\//,} | |
} | |
function ph.common.get_class_dir { | |
local __class=$1 __basename | |
local -n ___dir=$2 | |
if [[ ${__class} == keywords ]]; then | |
if [[ -d /etc/portage/package.accept_keywords ]]; then | |
___dir=/etc/portage/package.accept_keywords | |
elif [[ -d /etc/portage/package.keywords ]]; then | |
___dir=/etc/portage/package.keywords | |
else | |
die "No package.accept_keywords or package.keywords directory was found in /etc/portage." | |
fi | |
else | |
if [[ ${__class} == restrict ]]; then | |
__basename=package.accept_restrict | |
else | |
__basename=package.${__class} | |
fi | |
if [[ -d /etc/portage/${__basename} ]]; then | |
___dir=/etc/portage/${__basename} | |
else | |
die "No ${__basename} directory found in /etc/portage." | |
fi | |
fi | |
} | |
function ph.common.get_filepath_equivalent { | |
local __class=$1 __pkg=$2 __dir __filename | |
local -n ___filepath=$3 | |
ph.common.get_class_dir "${__class}" __dir | |
ph.common.get_filename_equivalent "${__pkg}" __filename | |
___filepath=${__dir}/${__filename} | |
} | |
function ph.common.detect_lone_help_option { | |
local i | |
for (( i = 1; i <= $#; ++i )); do | |
case ${!i} in | |
-h|--help) | |
return 0 | |
;; | |
--) | |
breaks | |
;; | |
-[!-][!-]*) | |
set -- "${@:1:i - 1}" "${!i:0:2}" "-${!i:2}" "${@:i + 1}" | |
(( --i )) | |
;; | |
-?*) | |
die "Invalid option: $1" 2 | |
;; | |
*) | |
;; | |
esac | |
done | |
return 1 | |
} | |
function ph.common.get_arguments { | |
local -n __args=$1 | |
local __ | |
shift | |
__args=() | |
while __=${1-}; shift; do | |
if [[ $__ == -- ]]; then | |
__args+=("${@:2}") | |
break | |
elif [[ $__ != -?* ]]; then | |
__args+=("$__") | |
fi | |
done | |
[[ ${__args+.} ]] | |
} | |
function ph.common.has_arguments { | |
local void | |
ph.common.get_arguments void "$@" | |
} | |
function ph.common.disallow_options_blending { | |
[[ $# -lt 2 ]] || die "The '${opts}' option can't be specified along with '${opts[1]}'." | |
} | |
function ph.world.show_usage_and_exit { | |
print_mnemonic "Usage: ph _world [-h|--help] [subcommand]" | |
print_mnemonic "Subcommands: _add _find _list _remove _edit _print" | |
exit 2 | |
} | |
function ph.world.add.show_usage_and_exit { | |
echo "Adds PKG to the world file" | |
print_mnemonic "Usage: ph _world _add <pkg> ..." | |
exit 2 | |
} | |
function ph.world.add { | |
local pkg args entry_added=false | |
ph.common.check_external_command grep sed sort sponge | |
ph.common.detect_lone_help_option "$@" && ph.world.add.show_usage_and_exit | |
ph.common.get_arguments args "$@" | |
[[ ${args+.} ]] || die "No package names specified." 2 | |
for pkg in "${args[@]}"; do | |
ph.common.check_package_name "${pkg}" | |
done | |
for pkg in "${args[@]}"; do | |
if grep -Fxe "${pkg}" "${_PH_WORLD_FILE}" >/dev/null; then | |
echo "Package name alredy added in world file: ${pkg}" | |
else | |
if [[ ${entry_added} == false ]]; then | |
echo "Creating a backup copy of the world file." | |
cp -a "${_PH_WORLD_FILE}" "${_PH_WORLD_FILE}.bak.new" || \ | |
die "Failed to create a backup copy of the world file." | |
fi | |
echo "Adding '${pkg}' to world file." | |
printf '\n%s\n' "${pkg}" >> "${_PH_WORLD_FILE}" || { | |
err "Failed to add '${pkg}' to world file." | |
die "Consider comparing the world file with '${_PH_WORLD_FILE}.bak.new'." | |
} | |
entry_added=true | |
fi | |
done | |
if [[ ${entry_added} == true ]]; then | |
echo "Sorting world file entries." | |
sort -Vu "${_PH_WORLD_FILE}" | sed '/^[[:space:]]*$/d' | sponge "${_PH_WORLD_FILE}" || { | |
err "Failed to sort world file." | |
die "Please compare it with '${_PH_WORLD_FILE}.bak.new'." | |
} | |
echo "Saving the new backup file as '${_PH_WORLD_FILE}.bak'." | |
mv -- "${_PH_WORLD_FILE}.bak.new" "${_PH_WORLD_FILE}.bak" || \ | |
die "Failed to save '${_PH_WORLD_FILE}.bak.new' as ${_PH_WORLD_FILE}.bak." | |
fi | |
} | |
function ph.world.find.show_usage_and_exit { | |
echo "Finds packags entrie containing the specified plain text expressions" | |
print_mnemonic "Usage: ph _world _find <expression> ..." | |
exit 2 | |
} | |
function ph.world.find { | |
local expressions grep_cmd __ | |
ph.common.check_external_command grep | |
ph.common.detect_lone_help_option "$@" && ph.world.find.show_usage_and_exit | |
ph.common.get_arguments expressions "$@" | |
[[ ${expressions+.} ]] || die "No expressions specified." 2 | |
for __ in "${expressions[@]}"; do | |
[[ -z $__ ]] && die "Invalid empty expression: $__" 2 | |
done | |
grep_cmd="grep -hFe ${expressions@Q} ${_PH_WORLD_FILE@Q}" | |
for __ in "${expressions[@]:2}"; do | |
grep_cmd+=" | grep -Fe ${__@Q}" | |
done | |
eval "${grep_cmd}" | |
} | |
function ph.world.list.show_usage_and_exit { | |
echo "Shows the world file's contents" | |
print_mnemonic "Usage: ph _world _list" | |
exit 2 | |
} | |
function ph.world.list { | |
local args | |
ph.common.check_external_command cat | |
ph.common.detect_lone_help_option "$@" && ph.world.list.show_usage_and_exit | |
ph.common.get_arguments args "$@" && die "Arguments are unexpected: ${args[*]@Q}" 2 | |
cat "${_PH_WORLD_FILE}" | |
} | |
function ph.world.remove.show_usage_and_exit { | |
echo "Removes PKG from the world file" | |
print_mnemonic "Usage: ph _world _remove <pkg> ..." | |
exit 2 | |
} | |
function ph.world.remove { | |
local pkg args entry_removed=false | |
ph.common.check_external_command cp grep mv sponge | |
ph.common.detect_lone_help_option "$@" && ph.world.remove.show_usage_and_exit | |
ph.common.get_arguments args "$@" | |
[[ ${args+.} ]] || die "No package names specified." 2 | |
for pkg in "${args[@]}"; do | |
ph.common.check_package_name "${pkg}" | |
done | |
for pkg in "${args[@]}"; do | |
if grep -Fxe "${pkg}" "${_PH_WORLD_FILE}" >/dev/null; then | |
if [[ ${entry_removed} == false ]]; then | |
echo "Creating a backup copy of the world file." | |
cp -a "${_PH_WORLD_FILE}" "${_PH_WORLD_FILE}.bak.new" || \ | |
die "Failed to create a backup copy of the world file." | |
fi | |
echo "Removing '${pkg}' from the world file." | |
grep -vFxe "${pkg}" "${_PH_WORLD_FILE}" | sponge "${_PH_WORLD_FILE}" || { | |
err "Failed to remove '${pkg}' from the world file." | |
die "Consider comparing the world file with '${_PH_WORLD_FILE}.bak.new'." | |
} | |
entry_removed=true | |
else | |
echo "Package name not included in world file: ${pkg}" | |
fi | |
done | |
if [[ ${entry_removed} == true ]]; then | |
echo "Saving the new backup file as '${_PH_WORLD_FILE}.bak'." | |
mv -- "${_PH_WORLD_FILE}.bak.new" "${_PH_WORLD_FILE}.bak" || \ | |
die "Failed to save '${_PH_WORLD_FILE}.bak.new' as ${_PH_WORLD_FILE}.bak." | |
fi | |
} | |
function ph.world.edit.show_usage_and_exit { | |
echo "Opens the world file with an editor" | |
print_mnemonic "Usage: ph _world _edit" | |
exit 2 | |
} | |
function ph.world.edit { | |
ph.common.detect_lone_help_option "$@" && ph.world.edit.show_usage_and_exit | |
ph.common.has_arguments "$@" && die "The 'edit' command does not expect arguments." | |
${EDITOR:-vi} "${_PH_WORLD_FILE}" | |
} | |
function ph.world.print.show_usage_and_exit { | |
echo "Displays '${_PH_WORLD_FILE}'" | |
print_mnemonic "Usage: ph _world _print" | |
exit 2 | |
} | |
function ph.world.print { | |
ph.common.detect_lone_help_option "$@" && ph.world.print.show_usage_and_exit | |
ph.common.has_arguments "$@" && die "The 'print' command does not expect arguments." | |
echo "${_PH_WORLD_FILE}" | |
} | |
function ph.world { | |
local command=() | |
local -A COMMAND_MAP=([a]=add [f]=find [l]=list [r]=remove [e]=edit [p]=print) | |
while [[ $# -gt 0 && -z ${command+.} ]]; do | |
case $1 in | |
add|find|list|remove|edit|print) | |
command=$1 | |
;; | |
[aflrep]) | |
command=${COMMAND_MAP[$1]} | |
;; | |
-h|--help) | |
ph.world.show_usage_and_exit | |
;; | |
--) | |
break | |
;; | |
-[!-][!-]*) | |
set -- "${1:0:2}" "-${1:2}" "${@:2}" | |
continue | |
;; | |
-*) | |
die "Invalid option: $1" 2 | |
;; | |
*) | |
die "Invalid subcommand: $1" 2 | |
;; | |
esac | |
shift | |
done | |
"ph.world.${command-list}" "$@" | |
} | |
function ph.main.get_ls_command { | |
local -n ___rvar=$1 | |
if [[ -z ${_PH_LS_COMMAND+.} ]]; then | |
local columns=${COLUMNS:-$(type -P tput >/dev/null && tput cols)} | |
_PH_LS_COMMAND=(ls -C --color=auto --literal ${columns:+"--width=${columns}"}) | |
fi | |
___rvar=("${_PH_LS_COMMAND[@]}") | |
} | |
function ph.main.show_usage_and_exit { | |
echo "Usage: ph class [-h|--help] [--] [arguments]" | |
print_mnemonic "Classes: _use _keywords _mask u_nmask _license _env _restrict _world" | |
exit 2 | |
} | |
function ph.main.show_command_usage_and_exit { | |
local class=$1 argument_name class_dir ls_command usage=() usage_details=() __ | |
local -A MNEMONIC_MAP=([use]=_use [keywords]=_keywords [mask]=_mask [unmask]=u_nmask | |
[license]=_license [env]=_env [restrict]=_restrict) | |
case ${class} in | |
mask|unmask) | |
usage=("<pkg>") | |
usage_details=("${class@u}s specified packages") | |
;; | |
keywords) | |
usage=("<pkg> [<options>] [--] [<keyword> ...]") | |
usage_details=("Keyword-unmasks specified package") | |
usage_details+=("Specific to keywords, an entry is still added even if no keyword was specified.") | |
ph. | |
argument_name="keyword" | |
;; | |
use) | |
usage=("<pkg> [<options>] [--] <use_flag> ...") | |
usage_details=("Sets USE flags on package") | |
argument_name="USE flag" | |
;; | |
license) | |
usage=("<pkg> [<options>] [--] <license> ...") | |
usage_details=("Sets licenses on package") | |
argument_name="license" | |
;; | |
env) | |
usage=("<pkg> [<options>] [--] <env_filename> ...") | |
usage_details=("Sets env entries on package") | |
argument_name="env filename" | |
;; | |
restrict) | |
usage=("<pkg> [<options>] [--] <restrict_token> ...") | |
usage_details=("Sets restrict tokens on package") | |
argument_name="restrict token" | |
;; | |
esac | |
printf '%s\n\n' "${usage_details}" | |
print_mnemonic "Usage: ph ${MNEMONIC_MAP[${class}]} ${usage//_/__}" | |
print_mnemonic " ph ${MNEMONIC_MAP[${class}]} <pkg> --s|--show" | |
for __ in "${usage[@]:1}"; do | |
print_mnemonic " pkg ${MNEMONIC_MAP[${class}]} ${__//_/__}" | |
done | |
print_mnemonic " ph ${MNEMONIC_MAP[${class}]} <options>" | |
print_mnemonic " ph ${MNEMONIC_MAP[${class}]}" | |
ph.common.get_class_dir "${class}" class_dir | |
ph.main.get_ls_command ls_command | |
echo " | |
Options: | |
-h, --help Show this help info | |
-s, --show Display all entries in the class directory, or just | |
the package's filename if a package is specified | |
Package-relevant options: | |
-e, --edit Open an editor to edit the package's existing | |
equivalent file | |
-f, --filename=FILENAME Use FILENAME as filename instead of the package | |
name's equivalent | |
Class-relevant options: | |
-b, --browse Open a filename manager using xdg-open | |
-l, --list Show lists of files in class directory | |
-L, --ls Show list of files in class directory using an ls command: | |
${ls_command[*]} | |
-p, --print-dir Display complete path name of the class directory | |
Class directory: ${class_dir} | |
" | |
[[ ${usage_details[1]+.} ]] && printf '%s\n' "${usage_details[@]:1}" | |
echo "Command defaults to '--show' behavior when no package or option is specified." | |
if [[ ${class} == @(keywords|use|license|restrict) ]]; then | |
echo " | |
Specifying '--' before a ${argument_name} is recommended to avoid negations being | |
interpreted as invalid options." | |
fi | |
exit 2 | |
} | |
function ph.main.add_argument { | |
local class=$1 pkg=$2 argument=("${@:3:1}") dir entry_found=false filepath lines=() | |
ph.common.check_package_name "${pkg}" | |
if [[ ${3-} == --filename=* ]]; then | |
ph.common.get_class_dir "${class}" dir | |
filepath=${dir}/${3#--filename=} | |
else | |
ph.common.get_filepath_equivalent "${class}" "${pkg}" filepath | |
fi | |
if [[ -e ${filepath} ]]; then | |
readarray -t lines < "${filepath}" | |
local output=() IFS=$' \t\n' | |
for line in "${lines[@]}"; do | |
local words=(${line}) | |
if [[ ${words} == "${pkg}" ]]; then | |
if [[ ${entry_found} == true ]]; then | |
die "Double entry of '${pkg}' found in '${filepath}'." | |
else | |
entry_found=true | |
fi | |
for word in "${words[@]:1}"; do | |
[[ ${word} == "${argument-}" ]] && return | |
done | |
[[ ${argument+.} ]] && words+=("${argument}") | |
line=${pkg}${words[1]+ ${words[*]:1}} | |
fi | |
output+=("${line}") | |
done | |
if [[ ${entry_found} == true ]]; then | |
printf '%s\n' "${output[@]}" > "${filepath}" | |
return | |
fi | |
fi | |
printf '%s\n' "${lines[@]}" "${pkg}${argument+ }${argument-}" > "${filepath}" | |
} | |
function ph.main.display_or_edit_package_file_contents { | |
local class=$1 pkg=$2 mode=$3 filename=(${4+"$4"}) dir filepath | |
ph.common.check_external_command cat | |
if [[ ${filename+.} ]]; then | |
ph.common.get_class_dir "${class}" dir | |
filepath=${dir}/${filename} | |
else | |
ph.common.check_package_name "${pkg}" | |
ph.common.get_filepath_equivalent "${class}" "${pkg}" filepath | |
fi | |
if [[ ${mode} == edit ]]; then | |
${EDITOR:-vi} "${filepath}" | |
elif [[ -e ${filepath} ]]; then | |
ph.common.check_external_command cat | |
cat "${filepath}" | |
else | |
die "Entry file doesn't exist: ${filepath}" | |
fi | |
} | |
function ph.main.display_package_file_contents { | |
ph.main.display_or_edit_package_file_contents "$1" "$2" display "${@:3}" | |
} | |
function ph.main.edit_package_file_contents { | |
ph.main.display_or_edit_package_file_contents "$1" "$2" edit "${@:3}" | |
} | |
function ph.main.run_command { | |
local class=$1 args=() browse_opt=() class_dir edit_opt=() filename=() filename_opt=() \ | |
list_opt=() ls_opt=() opts pkg print_dir_opt=() show_opt=() __ | |
shift | |
if [[ ${class} == world ]]; then | |
ph.world "$@" | |
return | |
fi | |
while [[ $# -gt 0 ]]; do | |
case $1 in | |
-b|--browse) | |
browse_opt=$1 | |
;; | |
-e|--edit) | |
edit_opt=$1 | |
;; | |
-f|--filename?(=*)) | |
filename_opt=${1%%=*} | |
get_opt_and_optarg "${@:1:2}" | |
filename=${OPTARG} | |
;; | |
-h|--help) | |
ph.main.show_command_usage_and_exit "${class}" | |
;; | |
-l|--list) | |
list_opt=$1 | |
;; | |
-L|--ls) | |
ls_opt=$1 | |
;; | |
-p|--print-dir) | |
print_dir_opt=$1 | |
;; | |
-s|--show) | |
show_opt=$1 | |
;; | |
--) | |
args+=("${@:2}") | |
break | |
;; | |
-[!-][!-]*) | |
set -- "${1:0:2}" "-${1:2}" "${@:2}" | |
continue | |
;; | |
-?*) | |
die "Invalid option: $1" 2 | |
;; | |
*) | |
args+=("$1") | |
;; | |
esac | |
shift | |
done | |
[[ ${filename+.} && ${filename} == @(|.|..|*/*) ]] && \ | |
die "Invalid filename specified: ${filename}" | |
ph.common.disallow_options_blending ${edit_opt-} ${list_opt-} ${ls_opt-} ${print_dir_opt-} ${show_opt-} | |
ph.common.disallow_options_blending ${filename_opt-} ${list_opt-} ${ls_opt-} ${print_dir_opt-} | |
set -- "${args[@]}" | |
if [[ $# -gt 0 ]]; then | |
for __ in ${list_opt-} ${ls_opt-} ${print_dir_opt-}; do | |
die "The '$__' option can't be specified with a package." 2 | |
done | |
pkg=$1 | |
shift | |
if [[ $# -eq 0 ]]; then | |
if [[ ${edit_opt+.} ]]; then | |
ph.main.edit_package_file_contents "${class}" "${pkg}" "${filename[@]}" | |
elif [[ ${show_opt+.} ]]; then | |
ph.main.display_package_file_contents "${class}" "${pkg}" "${filename[@]}" | |
elif [[ ${class} == @(keywords|mask|unmask) ]]; then | |
ph.main.add_argument "${class}" "${pkg}" ${filename+--filename="${filename}"} | |
else | |
die "The '${class}' command requires an argument to the package." | |
fi | |
else | |
[[ ${class} == @(mask|unmask) ]] && \ | |
die "The '${class}' command does not accept package arguments: $*" | |
[[ ${opts+.} ]] && die \ | |
die "The '${opts}' option can't be specified along with package arguments: $*" | |
for __; do | |
ph.main.add_argument "${class}" "${pkg}" "${filename_opt[@]}" "$__" | |
done | |
fi | |
elif [[ ${filename+.} && (${edit_opt+.} || ${show_opt+.}) ]]; then | |
if [[ ${edit_opt+.} ]]; then | |
ph.main.edit_package_file_contents "${class}" - "${filename}" | |
elif [[ ${show_opt+.} ]]; then | |
ph.main.display_package_file_contents "${class}" - "${filename}" | |
fi | |
else | |
[[ ${edit_opt+.} ]] && \ | |
die "The '${edit_opt}' option needs to be specified with a package or the '--filename' option." | |
[[ ${filename_opt+.} ]] && \ | |
die "The '${filename_opt}' option needs to be specified with a package, the '--edit' option or the '--show' option." | |
ph.common.get_class_dir "${class}" class_dir | |
if [[ ${list_opt+.} ]]; then | |
( | |
cd "${class_dir}" || exit | |
set +f | |
files=() dirs=() | |
for f in *; do | |
if [[ -d $f ]]; then | |
dirs+=("$f") | |
else | |
files+=("$f") | |
fi | |
done | |
[[ ${dirs+.]} ]] && printf '%s/\n' "${dirs[@]}" | |
[[ ${files+.]} ]] && printf '%s\n' "${files[@]}" | |
) | |
elif [[ ${ls_opt+.} ]]; then | |
( | |
ph.main.get_ls_command ls_command | |
cd "${class_dir}" && exec "${ls_command[@]}" | |
) | |
elif [[ ${print_dir_opt+.} ]]; then | |
echo "${class_dir}" | |
else | |
ph.common.check_external_command grep sort | |
set +f | |
set -- "${class_dir}"/* | |
set -f | |
[[ $# -gt 0 ]] && grep -vhEe '^\s*(#|$)' "$@" | sort -V | |
fi | |
fi | |
} | |
function ph.main { | |
local class=() __ | |
local -A CLASS_MAP=([u]=use [k]=keywords [m]=mask [n]=unmask [l]=license [e]=env [r]=restrict [w]=world) | |
while [[ $# -gt 0 && -z ${class+.} ]]; do | |
case $1 in | |
[ukmnlerw]) | |
class=${CLASS_MAP[$1]} | |
;; | |
use|keywords|mask|unmask|license|env|restrict|world) | |
class=$1 | |
;; | |
-h|--help) | |
ph.main.show_usage_and_exit | |
;; | |
--) | |
die "Unexpected separator: $1" 2 | |
;; | |
-[!-][!-]*) | |
set -- "${1:0:2}" "-${1:2}" "${@:2}" | |
continue | |
;; | |
-?*) | |
die "Invalid option: $1" 2 | |
;; | |
*) | |
die "Invalid class: $1" 2 | |
;; | |
esac | |
shift | |
done | |
if [[ -z ${class+.} ]]; then | |
err "Class not specified." | |
ph.main.show_usage_and_exit >&2 | |
fi | |
ph.main.run_command "${class}" "$@" | |
} | |
ph.main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment