feat: add Nivora CLI and msvc-go-wine

This commit is contained in:
Александр
2026-07-25 01:43:11 +10:00
parent 3cc4217790
commit fdb4a492ef
22 changed files with 949 additions and 413 deletions
+546
View File
@@ -0,0 +1,546 @@
#!/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 "$@"