mirror of
https://github.com/Cheviiot/Nivora.git
synced 2026-08-03 15:51:12 +00:00
547 lines
18 KiB
Bash
Executable File
547 lines
18 KiB
Bash
Executable File
#!/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 "$@"
|