Files

547 lines
18 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
set -euo pipefail
readonly NIVORA_VERSION='1.0.0'
readonly DEFAULT_REPOSITORY='nivora'
language="${NIVORA_LANG:-}"
repository="${NIVORA_REPO:-${DEFAULT_REPOSITORY}}"
dry_run=0
color_mode="${NIVORA_COLOR:-auto}"
detect_language() {
[[ -n "$language" ]] && return
case "${LC_ALL:-${LC_MESSAGES:-${LANG:-en}}}" in
ru* | RU*) language='ru' ;;
*) language='en' ;;
esac
}
message() {
local key="$1"
case "${language}:${key}" in
ru:title) printf 'Простое управление пакетами Stapler' ;;
ru:usage) printf 'Использование' ;;
ru:packages) printf 'Пакеты' ;;
ru:system) printf 'Система' ;;
ru:install) printf 'Установить' ;;
ru:remove) printf 'Удалить' ;;
ru:upgrade) printf 'Обновить' ;;
ru:search) printf 'Найти' ;;
ru:info) printf 'Информация' ;;
ru:all_packages) printf 'Все пакеты' ;;
ru:refresh) printf 'Обновить репозитории' ;;
ru:repositories) printf 'Репозитории' ;;
ru:fix) printf 'Исправить' ;;
ru:doctor) printf 'Диагностика' ;;
ru:config) printf 'Конфигурация' ;;
ru:help) printf 'Справка' ;;
ru:exit) printf 'Выход' ;;
ru:choose) printf 'Выберите действие' ;;
ru:package_prompt) printf 'Имя пакета' ;;
ru:query_prompt) printf 'Поисковый запрос' ;;
ru:unknown_command) printf 'неизвестная команда' ;;
ru:unknown_choice) printf 'нет такого пункта' ;;
ru:missing_argument) printf 'необходим аргумент' ;;
ru:invalid_language) printf 'поддерживаются языки ru и en' ;;
ru:invalid_repository) printf 'некорректное имя репозитория' ;;
ru:stapler_missing) printf 'команда stplr не найдена' ;;
ru:privilege_missing) printf 'команда повышения привилегий не найдена' ;;
ru:running) printf 'Выполняется' ;;
ru:preview) printf 'Предпросмотр' ;;
ru:diagnostics) printf 'Диагностика окружения' ;;
ru:ready) printf 'Система готова к работе' ;;
ru:problems) printf 'Обнаружены проблемы' ;;
ru:available) printf 'доступен' ;;
ru:missing) printf 'не найден' ;;
ru:language) printf 'Язык' ;;
ru:repository) printf 'Репозиторий' ;;
ru:privileges) printf 'Привилегии' ;;
ru:commands) printf 'Короткие команды' ;;
ru:options) printf 'Параметры' ;;
ru:examples) printf 'Примеры' ;;
en:title) printf 'Simple Stapler package management' ;;
en:usage) printf 'Usage' ;;
en:packages) printf 'Packages' ;;
en:system) printf 'System' ;;
en:install) printf 'Install' ;;
en:remove) printf 'Remove' ;;
en:upgrade) printf 'Upgrade' ;;
en:search) printf 'Search' ;;
en:info) printf 'Information' ;;
en:all_packages) printf 'All packages' ;;
en:refresh) printf 'Refresh repositories' ;;
en:repositories) printf 'Repositories' ;;
en:fix) printf 'Repair' ;;
en:doctor) printf 'Diagnostics' ;;
en:config) printf 'Configuration' ;;
en:help) printf 'Help' ;;
en:exit) printf 'Exit' ;;
en:choose) printf 'Choose an action' ;;
en:package_prompt) printf 'Package name' ;;
en:query_prompt) printf 'Search query' ;;
en:unknown_command) printf 'unknown command' ;;
en:unknown_choice) printf 'unknown choice' ;;
en:missing_argument) printf 'an argument is required' ;;
en:invalid_language) printf 'supported languages are ru and en' ;;
en:invalid_repository) printf 'invalid repository name' ;;
en:stapler_missing) printf 'stplr command was not found' ;;
en:privilege_missing) printf 'privilege command was not found' ;;
en:running) printf 'Running' ;;
en:preview) printf 'Preview' ;;
en:diagnostics) printf 'Environment diagnostics' ;;
en:ready) printf 'The system is ready' ;;
en:problems) printf 'Problems found' ;;
en:available) printf 'available' ;;
en:missing) printf 'missing' ;;
en:language) printf 'Language' ;;
en:repository) printf 'Repository' ;;
en:privileges) printf 'Privileges' ;;
en:commands) printf 'Short commands' ;;
en:options) printf 'Options' ;;
en:examples) printf 'Examples' ;;
*) printf '%s' "$key" ;;
esac
}
setup_style() {
if [[ "$color_mode" == 'never' || -n "${NO_COLOR:-}" || ! -t 1 ]]; then
bold=''; dim=''; cyan=''; blue=''; green=''; red=''; reset=''
return
fi
bold=$'\033[1m'
dim=$'\033[2m'
cyan=$'\033[38;5;44m'
blue=$'\033[38;5;75m'
green=$'\033[38;5;78m'
red=$'\033[38;5;203m'
reset=$'\033[0m'
}
fail() {
printf '%s%sNivora:%s %s\n' "$bold" "$red" "$reset" "$*" >&2
exit 2
}
validate_settings() {
case "$language" in
ru | en) ;;
*)
local invalid_language="$language"
language='en'
fail "$(message invalid_language): ${invalid_language}"
;;
esac
if [[ "$repository" != 'none' &&
! "$repository" =~ ^[[:alnum:]_.-]+$ ]]; then
fail "$(message invalid_repository): ${repository}"
fi
}
parse_options() {
arguments=()
while [[ "$#" -gt 0 ]]; do
case "$1" in
--lang)
[[ "$#" -ge 2 ]] || fail "$(message missing_argument): --lang"
language="$2"
shift 2
;;
--lang=*)
language="${1#--lang=}"
shift
;;
--repo)
[[ "$#" -ge 2 ]] || fail "$(message missing_argument): --repo"
repository="$2"
shift 2
;;
--repo=*)
repository="${1#--repo=}"
shift
;;
--dry-run)
dry_run=1
shift
;;
--no-color)
color_mode='never'
shift
;;
--)
shift
arguments+=("$@")
break
;;
*)
arguments+=("$1")
shift
;;
esac
done
setup_style
validate_settings
}
print_header() {
printf '%s%s╭─ NIVORA ─────────────────────────────╮%s\n' "$bold" "$cyan" "$reset"
if [[ "$language" == 'ru' ]]; then
printf '%s%s│%s Простое управление пакетами Stapler %s%s│%s\n' \
"$cyan" "$bold" "$reset" "$bold" "$cyan" "$reset"
else
printf '%s%s│%s Simple Stapler package management %s%s│%s\n' \
"$cyan" "$bold" "$reset" "$bold" "$cyan" "$reset"
fi
printf '%s%s╰──────────────────────────────────────╯%s\n' "$bold" "$cyan" "$reset"
}
menu_row() {
local key="$1"
local label="$2"
local shortcut="$3"
local padding=$((23 - ${#label}))
[[ "$padding" -lt 1 ]] && padding=1
printf ' %s[%s]%s %s' "$cyan" "$key" "$reset" "$label"
printf '%*s' "$padding" ''
printf '%s%s%s\n' "$dim" "$shortcut" "$reset"
}
interactive_menu() {
if [[ ! -t 0 || ! -t 1 ]]; then
show_help
return
fi
print_header
printf '\n %s%s%s\n' "$bold" "$(message packages)" "$reset"
menu_row i "$(message install)" nvi
menu_row e "$(message remove)" nve
menu_row u "$(message upgrade)" nvu
menu_row s "$(message search)" nvs
menu_row q "$(message info)" nvqi
menu_row a "$(message all_packages)" nvqa
printf '\n %s%s%s\n' "$bold" "$(message system)" "$reset"
menu_row r "$(message refresh)" nvr
menu_row l "$(message repositories)" nvrl
menu_row f "$(message fix)" nvf
menu_row d "$(message doctor)" nvd
local help_label
help_label="$(message help)"
printf '\n %s[h]%s %s' "$cyan" "$reset" "$help_label"
printf '%*s' "$((23 - ${#help_label}))" ''
printf '%s[x]%s %s\n\n' "$cyan" "$reset" "$(message exit)"
printf '%s ' "$(message choose)"
read -r choice
case "$choice" in
i) prompt_and_dispatch install package ;;
e) prompt_and_dispatch remove package ;;
u) dispatch upgrade ;;
s) prompt_and_dispatch search query ;;
q) prompt_and_dispatch info package ;;
a) dispatch list ;;
r) dispatch refresh ;;
l) dispatch repo-list ;;
f) dispatch fix ;;
d) doctor ;;
h) show_help ;;
x | q!) return ;;
*) fail "$(message unknown_choice): ${choice}" ;;
esac
}
prompt_and_dispatch() {
local command_name="$1"
local prompt_type="$2"
local value
if [[ "$prompt_type" == 'package' ]]; then
printf '%s ' "$(message package_prompt)"
else
printf '%s ' "$(message query_prompt)"
fi
read -r value
[[ -n "$value" ]] || fail "$(message missing_argument): ${command_name}"
dispatch "$command_name" "$value"
}
show_help() {
print_header
if [[ "$language" == 'ru' ]]; then
cat <<EOF
${bold}$(message usage)${reset}
nv интерактивное меню
nv <команда> [аргументы] выполнить команду
${bold}$(message commands)${reset}
${cyan}nvi${reset} <пакет...> установить ${cyan}nvr${reset} обновить репозитории
${cyan}nve${reset} <пакет...> удалить ${cyan}nvrl${reset} показать репозитории
${cyan}nvu${reset} обновить пакеты ${cyan}nvf${reset} исправить Stapler
${cyan}nvs${reset} <запрос> найти ${cyan}nvd${reset} запустить диагностику
${cyan}nvqi${reset} <пакет> информация ${cyan}nvc${reset} конфигурация Stapler
${cyan}nvqa${reset} каталог пакетов
${bold}$(message options)${reset}
--repo <имя|none> репозиторий коротких имён (по умолчанию: nivora)
--lang <ru|en> язык интерфейса
--dry-run показать команду без выполнения
--no-color отключить цвета
${bold}$(message examples)${reset}
nvi codex
nvs editor
nvqi codex
nv --repo none install other/package
EOF
else
cat <<EOF
${bold}$(message usage)${reset}
nv interactive menu
nv <command> [arguments] run a command
${bold}$(message commands)${reset}
${cyan}nvi${reset} <package...> install ${cyan}nvr${reset} refresh repositories
${cyan}nve${reset} <package...> remove ${cyan}nvrl${reset} list repositories
${cyan}nvu${reset} upgrade packages ${cyan}nvf${reset} repair Stapler
${cyan}nvs${reset} <query> search ${cyan}nvd${reset} run diagnostics
${cyan}nvqi${reset} <package> information ${cyan}nvc${reset} configure Stapler
${cyan}nvqa${reset} package catalog
${bold}$(message options)${reset}
--repo <name|none> repository for short names (default: nivora)
--lang <ru|en> interface language
--dry-run print the command without running it
--no-color disable colors
${bold}$(message examples)${reset}
nvi codex
nvs editor
nvqi codex
nv --repo none install other/package
EOF
fi
}
need_stapler() {
command -v stplr >/dev/null 2>&1 || fail "$(message stapler_missing)"
}
privilege_command() {
[[ "$EUID" -eq 0 ]] && return
local command_name="${NIVORA_SUDO:-sudo}"
command -v "$command_name" >/dev/null 2>&1 ||
fail "$(message privilege_missing): ${command_name}"
printf '%s\n' "$command_name"
}
is_package_name() {
local value="$1"
[[ -n "$value" &&
"$value" != -* &&
"$value" != */* &&
"$value" != ./* &&
"$value" != ../* &&
"$value" != /* &&
"$value" != *.rpm &&
"$value" != *.deb &&
"$value" != *.pkg.tar.* ]]
}
normalize_packages() {
local argument
for argument in "$@"; do
if [[ "$repository" != 'none' ]] && is_package_name "$argument"; then
printf '%s/%s\n' "$repository" "$argument"
else
printf '%s\n' "$argument"
fi
done
}
print_command() {
local label
if [[ "$dry_run" -eq 1 ]]; then
label="$(message preview)"
else
label="$(message running)"
fi
printf '%s%s%s%s %s%s' "$bold" "$blue" "$label" "$reset" "$cyan" "$reset" >&2
printf ' %q' "$@" >&2
printf '\n' >&2
}
run_stapler() {
local access="$1"
shift
need_stapler
local -a command=()
local privilege
if [[ "$access" == 'privileged' ]]; then
privilege="$(privilege_command)"
[[ -n "$privilege" ]] && command+=("$privilege")
fi
command+=(stplr "$@")
[[ "${NIVORA_QUIET:-0}" == '1' ]] || print_command "${command[@]}"
[[ "$dry_run" -eq 1 ]] && return
exec "${command[@]}"
}
require_arguments() {
local command_name="$1"
local count="$2"
[[ "$count" -gt 0 ]] || fail "$(message missing_argument): ${command_name}"
}
doctor() {
local failures=0
local privilege="${NIVORA_SUDO:-sudo}"
printf '%s%s%s\n\n' "$bold" "$(message diagnostics)" "$reset"
if command -v stplr >/dev/null 2>&1; then
printf ' %s●%s %-14s %s\n' "$green" "$reset" 'Stapler' "$(message available)"
else
printf ' %s●%s %-14s %s\n' "$red" "$reset" 'Stapler' "$(message missing)"
failures=1
fi
if [[ "$EUID" -eq 0 ]] || command -v "$privilege" >/dev/null 2>&1; then
printf ' %s●%s %-14s %s\n' \
"$green" "$reset" "$(message privileges)" \
"$([[ "$EUID" -eq 0 ]] && printf 'root' || printf '%s' "$privilege")"
else
printf ' %s●%s %-14s %s\n' \
"$red" "$reset" "$(message privileges)" "$(message missing)"
failures=1
fi
printf ' %s●%s %-14s %s\n' "$green" "$reset" "$(message language)" "$language"
printf ' %s●%s %-14s %s\n\n' \
"$green" "$reset" "$(message repository)" "$repository"
if [[ "$failures" -eq 0 ]]; then
printf '%s✓ %s%s\n' "$green" "$(message ready)" "$reset"
else
printf '%s✗ %s%s\n' "$red" "$(message problems)" "$reset"
fi
return "$failures"
}
completion() {
local shell="${1:-}"
case "$shell" in
bash)
cat <<'EOF'
_nivora() {
local commands='install remove upgrade search info list refresh repo-list fix doctor config completion version help'
if (( COMP_CWORD == 1 )); then
COMPREPLY=($(compgen -W "$commands" -- "${COMP_WORDS[COMP_CWORD]}"))
fi
}
complete -F _nivora nv
EOF
;;
fish)
cat <<'EOF'
complete -c nv -f -n '__fish_use_subcommand' -a 'install remove upgrade search info list refresh repo-list fix doctor config completion version help'
EOF
;;
zsh)
cat <<'EOF'
#compdef nv
_arguments '1:command:(install remove upgrade search info list refresh repo-list fix doctor config completion version help)' '*::argument:->arguments'
EOF
;;
*) fail "$(message missing_argument): completion <bash|fish|zsh>" ;;
esac
}
dispatch() {
local command_name="${1:-help}"
[[ "$#" -gt 0 ]] && shift
case "$command_name" in
install | i)
require_arguments "$command_name" "$#"
local -a packages=()
mapfile -t packages < <(normalize_packages "$@")
run_stapler privileged install "${packages[@]}"
;;
remove | erase | e)
require_arguments "$command_name" "$#"
local -a packages=()
mapfile -t packages < <(normalize_packages "$@")
run_stapler privileged remove "${packages[@]}"
;;
upgrade | update | u) run_stapler privileged up "$@" ;;
search | s)
require_arguments "$command_name" "$#"
run_stapler user search "$@"
;;
info | query-info | qi)
require_arguments "$command_name" "$#"
local -a packages=()
mapfile -t packages < <(normalize_packages "$@")
run_stapler user info "${packages[@]}"
;;
list | packages | query-all | qa) run_stapler user list "$@" ;;
refresh | r) run_stapler privileged refresh "$@" ;;
repo-list | rl) run_stapler user repo list "$@" ;;
repo)
if [[ "$#" -eq 0 ]]; then
run_stapler user repo list
elif [[ "${1:-}" == 'list' || "${1:-}" == 'ls' ]]; then
run_stapler user repo "$@"
else
run_stapler privileged repo "$@"
fi
;;
fix | f) run_stapler privileged fix "$@" ;;
doctor | d) doctor ;;
config | c)
if [[ "$#" -eq 0 ]]; then
run_stapler user config show
else
run_stapler user config "$@"
fi
;;
completion) completion "$@" ;;
support) run_stapler user support "$@" ;;
version | v | --version)
printf 'Nivora CLI %s\n' "$NIVORA_VERSION"
command -v stplr >/dev/null 2>&1 && stplr version
;;
help | h | -h | --help) show_help ;;
*) fail "$(message unknown_command): ${command_name}" ;;
esac
}
main() {
detect_language
setup_style
parse_options "$@"
set -- "${arguments[@]}"
case "$(basename "$0")" in
nv)
if [[ "$#" -eq 0 ]]; then interactive_menu; else dispatch "$@"; fi
;;
nvi) dispatch install "$@" ;;
nve) dispatch remove "$@" ;;
nvu) dispatch upgrade "$@" ;;
nvs) dispatch search "$@" ;;
nvqi) dispatch info "$@" ;;
nvqa) dispatch list "$@" ;;
nvr) dispatch refresh "$@" ;;
nvrl) dispatch repo-list "$@" ;;
nvf) dispatch fix "$@" ;;
nvd) doctor ;;
nvc) dispatch config "$@" ;;
nivora) dispatch "$@" ;;
*) fail "$(message unknown_command): $(basename "$0")" ;;
esac
}
main "$@"