mirror of
https://github.com/TECHNOFAB11/pay-respects.git
synced 2026-02-02 15:45:11 +01:00
chore: rearrange directories
This commit is contained in:
parent
d5fb7462e0
commit
4c9aac45a8
46 changed files with 11 additions and 18 deletions
25
core/Cargo.toml
Normal file
25
core/Cargo.toml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
[package]
|
||||
name = "pay-respects"
|
||||
authors = ["iff <iff@ik.me>"]
|
||||
version = "0.5.15"
|
||||
edition = "2021"
|
||||
|
||||
# for crates.io
|
||||
description = "Terminal command suggestion, alternative to thefuck written in Rust with AI support"
|
||||
homepage = "https://codeberg.org/iff/pay-respects"
|
||||
repository = "https://github.com/iffse/pay-respects"
|
||||
keywords = ["cli", "terminal", "utility", "shell"]
|
||||
categories = ["command-line-utilities"]
|
||||
license = "AGPL-3.0"
|
||||
include = ["**/*.rs", "**/*.toml"]
|
||||
|
||||
[dependencies]
|
||||
colored = "2"
|
||||
sys-locale = "0.3"
|
||||
rust-i18n = "3"
|
||||
regex-lite = "0.1"
|
||||
|
||||
inquire = "0.7.5"
|
||||
|
||||
pay-respects-parser = { version = "0.3.2", path = "../parser" }
|
||||
pay-respects-utils = {version = "0.1.0", path = "../utils"}
|
||||
228
core/i18n/i18n.toml
Normal file
228
core/i18n/i18n.toml
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
_version = 2
|
||||
|
||||
[help]
|
||||
en = '''
|
||||
Usage: %{usage}
|
||||
|
||||
%{eval}: Add the following line to your configuration file:
|
||||
%{eval_examples}
|
||||
|
||||
%{manual}: Add the following output to your configuration file
|
||||
%{manual_examples}
|
||||
'''
|
||||
es = '''
|
||||
Uso: %{usage}
|
||||
|
||||
%{eval}: Agrega la siguiente línea a tu archivo de configuración:
|
||||
%{eval_examples}
|
||||
|
||||
%{manual}: Agrega la siguiente salida a tu archivo de configuración
|
||||
%{manual_examples}
|
||||
'''
|
||||
de = '''
|
||||
Verwendung: %{usage}
|
||||
|
||||
%{eval}: Fügen Sie die folgende Zeile zu Ihrer Konfigurationsdatei hinzu:
|
||||
%{eval_examples}
|
||||
|
||||
%{manual}: Fügen Sie die folgende Ausgabe zu Ihrer Konfigurationsdatei hinzu:
|
||||
%{manual_examples}
|
||||
'''
|
||||
fr = '''
|
||||
Utilisation: %{usage}
|
||||
|
||||
%{eval}: Ajoutez la ligne suivante à votre fichier de configuration :
|
||||
%{eval_examples}
|
||||
|
||||
%{manual}: Ajoutez la sortie suivante à votre fichier de configuration :
|
||||
|
||||
%{manual_examples}
|
||||
'''
|
||||
it = '''
|
||||
Utilizzo: %{usage}
|
||||
|
||||
%{eval}: Aggiungi la seguente riga al tuo file di configurazione:
|
||||
%{eval_examples}
|
||||
|
||||
%{manual}: Aggiungi l'output seguente al tuo file di configurazione:
|
||||
%{manual_examples}
|
||||
'''
|
||||
pt = '''
|
||||
Uso: %{usage}
|
||||
|
||||
%{eval}: Adicione a seguinte linha ao seu arquivo de configuração:
|
||||
%{eval_examples}
|
||||
|
||||
%{manual}: Adicione a seguinte saída ao seu arquivo de configuração:
|
||||
%{manual_examples}
|
||||
'''
|
||||
ru = '''
|
||||
Использование: %{usage}
|
||||
|
||||
%{eval}: Добавьте следующую строку в ваш файл конфигурации:
|
||||
%{eval_examples}
|
||||
|
||||
%{manual}: Добавьте следующий вывод в ваш файл конфигурации:
|
||||
%{manual_examples}
|
||||
'''
|
||||
ja = '''
|
||||
使い方: %{usage}
|
||||
|
||||
%{eval}: 次の行を設定ファイルに追加してください:
|
||||
%{eval_examples}
|
||||
|
||||
%{manual}: 次の出力を設定ファイルに追加してください:
|
||||
%{manual_examples}
|
||||
'''
|
||||
ko = '''
|
||||
사용법: %{usage}
|
||||
|
||||
%{eval}: 다음 줄을 설정 파일에 추가하십시오:
|
||||
%{eval_examples}
|
||||
|
||||
%{manual}: 다음 출력을 설정 파일에 추가하십시오:
|
||||
%{manual_examples}
|
||||
'''
|
||||
zh = '''
|
||||
使用方法: %{usage}
|
||||
|
||||
%{eval}: 将以下行添加到您的配置文件中:
|
||||
%{eval_examples}
|
||||
|
||||
%{manual}: 将以下输出添加到您的配置文件中:
|
||||
%{manual_examples}
|
||||
'''
|
||||
|
||||
|
||||
[no-env-setup]
|
||||
en = "No %{var} in environment. Have you aliased the command with the correct argument?\n\nUse `%{help}` for help."
|
||||
es = "No se encontró %{var} en el entorno. ¿Has aliado el comando con el argumento correcto?\n\nUsa `%{help}` para obtener ayuda."
|
||||
de = "Kein %{var} in der Umgebung gefunden. Hast du den Befehl mit dem richtigen Argument verknüpft?\n\nBenutze `%{help}` für Hilfe."
|
||||
fr = "Aucun %{var} trouvé dans l'environnement. Avez-vous associé la commande avec le bon argument ?\n\nUtilisez `%{help}` pour obtenir de l'aide."
|
||||
it = "Nessun %{var} trovato nell'ambiente. Hai associato il comando con l'argomento corretto?\n\nUsa `%{help}` per ottenere aiuto."
|
||||
pt = "Nenhum %{var} encontrado no ambiente. Você associou o comando com o argumento correto?\n\nUse `%{help}` para obter ajuda."
|
||||
ru = "Переменная %{var} не найдена в окружении. Вы создали псевдоним для команды с правильным аргументом?\n\nИспользуйте `%{help}` для помощи."
|
||||
ja = "環境変数 %{var} が見つかりません。コマンドを正しい引数でエイリアスしましたか?\n\nヘルプを表示するには `%{help}` を使用してください。"
|
||||
ko = "환경 변수 %{var}을(를) 찾을 수 없습니다. 명령을 올바른 인수로 별칭 지정했습니까?\n\n도움말을 보려면 `%{help}`를 사용하십시오."
|
||||
zh = "在环境中找不到 %{var}。您是否使用正确的参数别名了命令?\n\n使用 `%{help}` 获取帮助。"
|
||||
|
||||
[no-shell]
|
||||
en = "No shell specified. Please specify a shell."
|
||||
es = "No se especificó ninguna shell. Por favor, especifica una shell."
|
||||
de = "Keine Shell angegeben. Bitte gib eine Shell an."
|
||||
fr = "Aucune shell spécifiée. Veuillez spécifier une shell."
|
||||
it = "Nessuna shell specificata. Specificare una shell."
|
||||
pt = "Nenhuma shell especificada. Por favor, especifique uma shell."
|
||||
ru = "Оболочка не указана. Укажите оболочку."
|
||||
ja = "シェルが指定されていません。シェルを指定してください。"
|
||||
ko = "쉘이 지정되지 않았습니다. 쉘을 지정하십시오."
|
||||
zh = "未指定 shell。请指定一个 shell。"
|
||||
|
||||
[multi-suggest]
|
||||
en = "%{num} suggestions found"
|
||||
es = "%{num} sugerencias encontradas"
|
||||
de = "%{num} Vorschläge gefunden"
|
||||
fr = "%{num} suggestions trouvées"
|
||||
it = "%{num} proposte trovate"
|
||||
pt = "%{num} sugestões encontradas"
|
||||
ru = "Найдено %{num} предложений"
|
||||
ja = "%{num} 件の提案が見つかりました"
|
||||
ko = "%{num} 개의 제안이 발견되었습니다"
|
||||
zh = "找到 %{num} 个建议"
|
||||
|
||||
[install-package]
|
||||
en = "Package(s) for the missing command found"
|
||||
es = "Paquete(s) para el comando faltante encontrado(s)"
|
||||
de = "Paket(e) für den fehlenden Befehl gefunden"
|
||||
fr = "Paquet(s) pour la commande manquante trouvé(s)"
|
||||
it = "Pacchetto(i) per il comando mancante trovato(i)"
|
||||
pt = "Pacote(s) para o comando ausente encontrado(s)"
|
||||
ru = "Найден пакет(ы) для отсутствующей команды"
|
||||
ja = "不足しているコマンドのパッケージが見つかりました"
|
||||
ko = "누락된 명령에 대한 패키지가 발견되었습니다"
|
||||
zh = "找到缺失命令的包"
|
||||
|
||||
[confirm]
|
||||
en = "Execute suggestion?"
|
||||
es = "¿Ejecutar sugerencia?"
|
||||
de = "Vorschlag ausführen?"
|
||||
fr = "Exécuter la suggestion?"
|
||||
it = "Eseguire la proposta?"
|
||||
pt = "Executar sugestão?"
|
||||
ru = "Выполнить предложение?"
|
||||
ja = "提案を実行しますか?"
|
||||
ko = "제안 실행?"
|
||||
zh = "执行建议?"
|
||||
|
||||
[confirm-yes]
|
||||
en = "Enter"
|
||||
es = "Entrar"
|
||||
de = "Eingabetaste"
|
||||
fr = "Entrée"
|
||||
it = "Invio"
|
||||
pt = "Entrar"
|
||||
ru = "Ввод"
|
||||
ja = "エンター"
|
||||
ko = "엔터"
|
||||
zh = "回车"
|
||||
|
||||
[retry]
|
||||
en = "Looking for new suggestion"
|
||||
es = "Buscando nueva sugerencia"
|
||||
de = "Suche nach neuem Vorschlag"
|
||||
fr = "Recherche d'une nouvelle suggestion"
|
||||
it = "Ricerca di una nuova proposta"
|
||||
pt = "Procurando nova sugestão"
|
||||
ru = "Поиск нового предложения"
|
||||
ja = "新しい提案を探しています"
|
||||
ko = "새 제안 찾는 중"
|
||||
zh = "寻找新建议"
|
||||
|
||||
[no-suggestion]
|
||||
en = "No suggestion found for command"
|
||||
es = "No se encontró ninguna sugerencia para el comando"
|
||||
de = "Kein Vorschlag für den Befehl gefunden"
|
||||
fr = "Aucune suggestion trouvée pour la commande"
|
||||
it = "Nessuna proposta trovata per il comando"
|
||||
pt = "Nenhuma sugestão encontrada para o comando"
|
||||
ru = "Предложение для команды не найдено"
|
||||
ja = "コマンドの提案が見つかりません"
|
||||
ko = "명령에 대한 제안을 찾을 수 없습니다"
|
||||
zh = "找不到命令的建议"
|
||||
|
||||
[command-not-found]
|
||||
en = "Command not found"
|
||||
es = "Comando no encontrado"
|
||||
de = "Befehl nicht gefunden"
|
||||
fr = "Commande introuvable"
|
||||
it = "Comando non trovato"
|
||||
pt = "Comando não encontrado"
|
||||
ru = "Команда не найдена"
|
||||
ja = "コマンドが見つかりません"
|
||||
ko = "명령을 찾을 수 없습니다"
|
||||
zh = "找不到命令"
|
||||
|
||||
[package-not-found]
|
||||
en = "No matching package found"
|
||||
es = "No se encontró ningún paquete coincidente"
|
||||
de = "Kein passendes Paket gefunden"
|
||||
fr = "Aucun paquet correspondant trouvé"
|
||||
it = "Nessun pacchetto corrispondente trovato"
|
||||
pt = "Nenhum pacote correspondente encontrado"
|
||||
ru = "Совпадающий пакет не найден"
|
||||
ja = "一致するパッケージが見つかりません"
|
||||
ko = "일치하는 패키지를 찾을 수 없습니다"
|
||||
zh = "找不到匹配的包"
|
||||
|
||||
[contribute]
|
||||
en = "If you think there should be a suggestion, please open an issue or send a pull request!"
|
||||
es = "Si crees que debería haber una sugerencia, ¡por favor abre un issue o envía un pull request!"
|
||||
de = "Wenn du denkst, dass es einen Vorschlag geben sollte, öffne bitte ein Issue oder sende einen Pull-Request!"
|
||||
fr = "Si vous pensez qu'il devrait y avoir une suggestion, veuillez ouvrir un ticket ou envoyer une demande de tirage !"
|
||||
it = "Se pensi che dovrebbe esserci una proposta, apri una issue o invia una pull request!"
|
||||
pt = "Se você acha que deveria haver uma sugestão, por favor abra uma issue ou envie um pull request!"
|
||||
ru = "Если вы считаете, что должно быть предложение, пожалуйста, откройте issue или отправьте pull request!"
|
||||
ja = "提案があるべきだと思う場合は、issueまたはpull requestを送信してください!"
|
||||
ko = "제안이 있어야 한다고 생각하는 경우 이슈를 열거나 풀 리퀘스트를 보내주세요!"
|
||||
zh = "如果您认为应该有建议,请打开一个 issue 或发送一个 pull request!"
|
||||
|
||||
17
core/rules/c_typo.toml
Normal file
17
core/rules/c_typo.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
command = "c"
|
||||
|
||||
[[match_err]]
|
||||
pattern = [
|
||||
"command not found",
|
||||
"unknown command",
|
||||
"nu::shell::external_command"
|
||||
]
|
||||
suggest = [
|
||||
'''
|
||||
#[length(2)]
|
||||
cd {{command[1:]}} ''',
|
||||
'''
|
||||
#[min_length(3)]
|
||||
cp {{command[1:]}} '''
|
||||
]
|
||||
|
||||
12
core/rules/cargo.toml
Normal file
12
core/rules/cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
command = "cargo"
|
||||
|
||||
[[match_err]]
|
||||
pattern = [
|
||||
"no such command"
|
||||
]
|
||||
suggest = [
|
||||
'''
|
||||
#[err_contains(did you mean)]
|
||||
{{command[0]}} {{err::(?:did you mean `)(.*)(?:`\?)}} {{command[2:]}} '''
|
||||
]
|
||||
|
||||
19
core/rules/cat.toml
Normal file
19
core/rules/cat.toml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
command = "cat"
|
||||
|
||||
[[match_err]]
|
||||
pattern = [
|
||||
"no such file or directory"
|
||||
]
|
||||
suggest = [
|
||||
'''
|
||||
cat {{typo[1](file)}} '''
|
||||
]
|
||||
|
||||
[[match_err]]
|
||||
pattern = [
|
||||
"is a directory"
|
||||
]
|
||||
suggest = [
|
||||
'''
|
||||
ls {{command[1:]}}'''
|
||||
]
|
||||
26
core/rules/cd.toml
Normal file
26
core/rules/cd.toml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
command = "cd"
|
||||
|
||||
[[match_err]]
|
||||
pattern = [
|
||||
"no such file or directory",
|
||||
"does not exist"
|
||||
]
|
||||
suggest = [
|
||||
'''
|
||||
cd {{typo[1](file)}}
|
||||
''',
|
||||
'''
|
||||
#[!shell(nu)]
|
||||
mkdir --parents {{command[1]}} && \
|
||||
cd {{command[1]}} '''
|
||||
]
|
||||
|
||||
[[match_err]]
|
||||
pattern = [
|
||||
"nu::shell::directory_not_found"
|
||||
]
|
||||
suggest = [
|
||||
'''
|
||||
mkdir {{command[1]}} and \
|
||||
cd {{command[1]}} '''
|
||||
]
|
||||
22
core/rules/cp.toml
Normal file
22
core/rules/cp.toml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
command = "cp"
|
||||
|
||||
[[match_err]]
|
||||
pattern = [
|
||||
"-r not specified",
|
||||
]
|
||||
suggest = [
|
||||
'''
|
||||
{{command[0]}} -r {{command[1:]}} '''
|
||||
]
|
||||
|
||||
[[match_err]]
|
||||
pattern = [
|
||||
"cannot create directory",
|
||||
]
|
||||
suggest = [
|
||||
'''
|
||||
#[err_contains(no such file or directory)]
|
||||
mkdir -p {{cmd::(?m)\s(\S+[\\\/])\S*\s*$}} && \
|
||||
{{command[0]}} {{opt::\s(-[\w]+)}} {{command[1:]}} '''
|
||||
]
|
||||
|
||||
111
core/rules/git.toml
Normal file
111
core/rules/git.toml
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
command = "git"
|
||||
|
||||
[[match_err]]
|
||||
pattern = [
|
||||
"is not a git command"
|
||||
]
|
||||
suggest = [
|
||||
'''
|
||||
{{command[0]}} {{typo[1](
|
||||
add,
|
||||
am,
|
||||
archive,
|
||||
bisect,
|
||||
branch,
|
||||
bundle,
|
||||
checkout,
|
||||
cherry-pick,
|
||||
citool,
|
||||
clean,
|
||||
clone,
|
||||
commit,
|
||||
describe,
|
||||
diff,
|
||||
fetch,
|
||||
format-patch,
|
||||
gc,
|
||||
gitk,
|
||||
grep,
|
||||
gui,
|
||||
init,
|
||||
log,
|
||||
maintenance,
|
||||
merge,
|
||||
mv,
|
||||
notes,
|
||||
pull,
|
||||
push,
|
||||
range-diff,
|
||||
rebase,
|
||||
reset,
|
||||
restore,
|
||||
revert,
|
||||
rm,
|
||||
scalar,
|
||||
shortlog,
|
||||
show,
|
||||
sparse-checkout,
|
||||
stash,
|
||||
status,
|
||||
submodule,
|
||||
switch,
|
||||
tag,
|
||||
worktree,
|
||||
config,
|
||||
fast-export,
|
||||
fast-import,
|
||||
filter-branch,
|
||||
mergetool,
|
||||
pack-refs,
|
||||
prune,
|
||||
reflog,
|
||||
remote,
|
||||
repack,
|
||||
replace,
|
||||
)}} {{command[2:]}}'''
|
||||
]
|
||||
|
||||
[[match_err]]
|
||||
pattern = [
|
||||
"did not match any file"
|
||||
]
|
||||
# alternatively `git checkout {{typo[2]({{shell(git branch | sed 's/^*//')}})}}`
|
||||
suggest = [
|
||||
'''
|
||||
#[cmd_contains(checkout)]
|
||||
git checkout {{typo[2]({{shell(git branch | sed 's/^*//')}})}} ''',
|
||||
'''
|
||||
#[cmd_contains(checkout)]
|
||||
git branch {{command[2]}} && \
|
||||
git checkout {{command[2]}} '''
|
||||
]
|
||||
|
||||
[[match_err]]
|
||||
pattern = [
|
||||
"has no upstream branch"
|
||||
]
|
||||
suggest = [
|
||||
'''
|
||||
#[cmd_contains(push)]
|
||||
git push --set-upstream origin {{shell(git rev-parse --abbrev-ref HEAD)}} '''
|
||||
]
|
||||
|
||||
[[match_err]]
|
||||
pattern = [
|
||||
"no tracking information for the current branch"
|
||||
]
|
||||
suggest = [
|
||||
'''
|
||||
#[cmd_contains(pull)]
|
||||
git pull --set-upstream origin {{shell(git rev-parse --abbrev-ref HEAD)}} '''
|
||||
]
|
||||
|
||||
[[match_err]]
|
||||
pattern = [
|
||||
"a branch named"
|
||||
]
|
||||
suggest = [
|
||||
'''
|
||||
#[cmd_contains(branch)]
|
||||
git checkout {{command[2]}} '''
|
||||
]
|
||||
11
core/rules/mkdir.toml
Normal file
11
core/rules/mkdir.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
command = "mkdir"
|
||||
|
||||
[[match_err]]
|
||||
pattern = [
|
||||
"cannot create directory"
|
||||
]
|
||||
suggest = [
|
||||
'''
|
||||
#[err_contains(no such file or directory)]
|
||||
{{command}} --parents '''
|
||||
]
|
||||
13
core/rules/mv.toml
Normal file
13
core/rules/mv.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
command = "mv"
|
||||
|
||||
[[match_err]]
|
||||
pattern = [
|
||||
"no such file or directory",
|
||||
"not a directory"
|
||||
]
|
||||
suggest = [
|
||||
'''
|
||||
mkdir --parents {{command[-1]}} && \
|
||||
{{command}} '''
|
||||
]
|
||||
|
||||
78
core/rules/npm.toml
Normal file
78
core/rules/npm.toml
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
command = "npm"
|
||||
|
||||
[[match_err]]
|
||||
pattern = [
|
||||
"unknown command"
|
||||
]
|
||||
suggest = [
|
||||
'''
|
||||
{{command[0]}} {{typo[1](
|
||||
access,
|
||||
adduser,
|
||||
audit,
|
||||
bugs,
|
||||
cache,
|
||||
ci,
|
||||
completion,
|
||||
config,
|
||||
dedupe,
|
||||
deprecate,
|
||||
diff,
|
||||
dist-tag,
|
||||
docs,
|
||||
doctor,
|
||||
edit,
|
||||
exec,
|
||||
explain,
|
||||
explore,
|
||||
find-dupes,
|
||||
fund,
|
||||
get,
|
||||
help,
|
||||
help-search,
|
||||
hook,
|
||||
init,
|
||||
install,
|
||||
install-ci-test,
|
||||
install-test,
|
||||
link,
|
||||
ll,
|
||||
login,
|
||||
logout,
|
||||
ls,
|
||||
org,
|
||||
outdated,
|
||||
owner,
|
||||
pack,
|
||||
ping,
|
||||
pkg,
|
||||
prefix,
|
||||
profile,
|
||||
prune,
|
||||
publish,
|
||||
query,
|
||||
rebuild,
|
||||
repo,
|
||||
restart,
|
||||
root,
|
||||
run-script,
|
||||
sbom,
|
||||
search,
|
||||
set,
|
||||
shrinkwrap,
|
||||
star,
|
||||
stars,
|
||||
start,
|
||||
stop,
|
||||
team,
|
||||
test,
|
||||
token,
|
||||
uninstall,
|
||||
unpublish,
|
||||
unstar,
|
||||
update,
|
||||
version,
|
||||
view,
|
||||
whoami
|
||||
)}} {{command[2:]}} '''
|
||||
]
|
||||
11
core/rules/pacman.toml
Normal file
11
core/rules/pacman.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
command = "pacman"
|
||||
|
||||
[[match_err]]
|
||||
pattern = [
|
||||
"no operation specified"
|
||||
]
|
||||
suggest = [
|
||||
'''
|
||||
{{command}} -Syu'''
|
||||
]
|
||||
|
||||
12
core/rules/pr_general.toml
Normal file
12
core/rules/pr_general.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
command = "_PR_general"
|
||||
|
||||
[[match_err]]
|
||||
pattern = [
|
||||
"command not found",
|
||||
"unknown command",
|
||||
"nu::shell::external_command"
|
||||
]
|
||||
suggest = [
|
||||
'''
|
||||
{{typo[0](path)}} {{command[1:]}} '''
|
||||
]
|
||||
37
core/rules/pr_privilege.toml
Normal file
37
core/rules/pr_privilege.toml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
command = "_PR_privilege"
|
||||
|
||||
[[match_err]]
|
||||
pattern = [
|
||||
"as root",
|
||||
"authentication is required",
|
||||
"be root",
|
||||
"be superuser",
|
||||
"cannot access",
|
||||
"eacces",
|
||||
"edspermissionerror",
|
||||
"insufficient privileges",
|
||||
"need root",
|
||||
"non-root users cannot",
|
||||
"not super-user",
|
||||
"only root can",
|
||||
"operation not permitted",
|
||||
"permission denied",
|
||||
"requires root",
|
||||
"root privilege",
|
||||
"root user",
|
||||
"sudorequirederror",
|
||||
"superuser privilege",
|
||||
"unless you are root",
|
||||
"can not open a temporary file",
|
||||
"use `sudo`",
|
||||
"you don't have access",
|
||||
"you don't have write permissions"
|
||||
]
|
||||
suggest = [
|
||||
'''
|
||||
#[executable(sudo)]
|
||||
sudo {{command}} ''',
|
||||
'''
|
||||
#[executable(doas)]
|
||||
doas {{command}} '''
|
||||
]
|
||||
22
core/rules/rm.toml
Normal file
22
core/rules/rm.toml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
command = "rm"
|
||||
|
||||
[[match_err]]
|
||||
pattern = [
|
||||
"is a directory",
|
||||
"try --recursive"
|
||||
]
|
||||
suggest = [
|
||||
'''
|
||||
{{command}} --recursive '''
|
||||
]
|
||||
|
||||
[[match_err]]
|
||||
pattern = [
|
||||
"no such file or directory",
|
||||
"file(s) not found"
|
||||
]
|
||||
suggest = [
|
||||
'''
|
||||
{{command[0}} {{opt::(?:\s)(-[\w]+)}} {{typo[1:](file)}} '''
|
||||
]
|
||||
|
||||
17
core/rules/touch.toml
Normal file
17
core/rules/touch.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
command = "touch"
|
||||
|
||||
[[match_err]]
|
||||
pattern = [ "no such file or directory" ]
|
||||
suggest = [
|
||||
'''
|
||||
mkdir --parents {{cmd::(?:\s)+(.*[\\\/])(?:\s)*}} && \
|
||||
touch {{command[1:]}} '''
|
||||
]
|
||||
|
||||
[[match_err]]
|
||||
pattern = [ "nu::shell::create_not_possible" ]
|
||||
suggest = [
|
||||
'''
|
||||
mkdir {{cmd::(?:\s)+(.*[\\\/])(?:\s)*}} and \
|
||||
touch {{command[1:]}} '''
|
||||
]
|
||||
64
core/rules/yarn.toml
Normal file
64
core/rules/yarn.toml
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
command = "yarn"
|
||||
|
||||
[[match_err]]
|
||||
pattern = [
|
||||
"not found"
|
||||
]
|
||||
suggest = [
|
||||
'''
|
||||
#[err_contains(command)]
|
||||
{{command[0]}} {{typo[1](
|
||||
access,
|
||||
add,
|
||||
audit,
|
||||
autoclean,
|
||||
bin,
|
||||
cache,
|
||||
check,
|
||||
config,
|
||||
create,
|
||||
exec,
|
||||
generate-lock-entry,
|
||||
generateLockEntry,
|
||||
global,
|
||||
help,
|
||||
import,
|
||||
info,
|
||||
init,
|
||||
install,
|
||||
licenses,
|
||||
link,
|
||||
list,
|
||||
login,
|
||||
logout,
|
||||
node,
|
||||
outdated,
|
||||
owner,
|
||||
pack,
|
||||
policies,
|
||||
publish,
|
||||
remove,
|
||||
run,
|
||||
tag,
|
||||
team,
|
||||
unlink,
|
||||
unplug,
|
||||
upgrade,
|
||||
upgrade-interactive,
|
||||
upgradeInteractive,
|
||||
version,
|
||||
versions,
|
||||
why,
|
||||
workspace,
|
||||
workspaces
|
||||
)}} {{command[2:]}} '''
|
||||
]
|
||||
|
||||
[[match_err]]
|
||||
pattern = [
|
||||
"error `install` has been replaced with `add` to add new dependencies."
|
||||
]
|
||||
suggest = [
|
||||
'''
|
||||
yarn add {{command[2:]}} '''
|
||||
]
|
||||
90
core/src/args.rs
Normal file
90
core/src/args.rs
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
use crate::shell::{initialization, Init};
|
||||
use colored::Colorize;
|
||||
|
||||
pub enum Status {
|
||||
Continue,
|
||||
Exit, // version, help, etc.
|
||||
Error,
|
||||
}
|
||||
|
||||
pub fn handle_args() -> Status {
|
||||
use Status::*;
|
||||
let args = std::env::args().collect::<Vec<String>>();
|
||||
if args.len() <= 1 {
|
||||
return Continue;
|
||||
}
|
||||
let mut init = Init::new();
|
||||
let mut index = 1;
|
||||
while index < args.len() {
|
||||
match args[index].as_str() {
|
||||
"-h" | "--help" => {
|
||||
print_help();
|
||||
return Exit;
|
||||
}
|
||||
"-v" | "--version" => {
|
||||
print_version();
|
||||
return Exit;
|
||||
}
|
||||
"-a" | "--alias" => {
|
||||
if args.len() > index + 1 {
|
||||
if args[index + 1].starts_with('-') {
|
||||
init.alias = String::from("f");
|
||||
} else {
|
||||
init.alias = args[index + 1].clone();
|
||||
index += 1;
|
||||
}
|
||||
} else {
|
||||
init.alias = String::from("f");
|
||||
}
|
||||
init.auto_alias = true;
|
||||
index += 1;
|
||||
}
|
||||
"--noncf" => {
|
||||
init.cnf = false;
|
||||
index += 1
|
||||
}
|
||||
_ => {
|
||||
init.shell = args[index].clone();
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if init.shell.is_empty() {
|
||||
eprintln!("{}", t!("no-shell"));
|
||||
return Error;
|
||||
}
|
||||
|
||||
init.binary_path = args[0].clone();
|
||||
|
||||
initialization(&mut init);
|
||||
Exit
|
||||
}
|
||||
|
||||
fn print_help() {
|
||||
println!(
|
||||
"{}",
|
||||
t!(
|
||||
"help",
|
||||
usage = "pay-respects <shell> [--alias [<alias>]] [--noncf]",
|
||||
eval = "Bash / Zsh / Fish".bold(),
|
||||
eval_examples = r#"
|
||||
eval "$(pay-respects bash --alias)"
|
||||
eval "$(pay-respects zsh --alias)"
|
||||
pay-respects fish --alias | source
|
||||
"#,
|
||||
manual = "Nushell / PowerShell".bold(),
|
||||
manual_examples = r#"
|
||||
pay-respects nushell [--alias <alias>]
|
||||
pay-respects pwsh [--alias <alias>] [--nocnf]
|
||||
"#
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
fn print_version() {
|
||||
println!(
|
||||
"version: {}",
|
||||
option_env!("CARGO_PKG_VERSION").unwrap_or("unknown")
|
||||
);
|
||||
}
|
||||
86
core/src/main.rs
Normal file
86
core/src/main.rs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
// pay-respects: Press F to correct your command
|
||||
// Copyright (C) 2023 iff
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published
|
||||
// by the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use sys_locale::get_locale;
|
||||
|
||||
mod args;
|
||||
mod modes;
|
||||
mod rules;
|
||||
mod shell;
|
||||
mod style;
|
||||
mod suggestions;
|
||||
mod system;
|
||||
|
||||
#[macro_use]
|
||||
extern crate rust_i18n;
|
||||
i18n!("i18n", fallback = "en", minify_key = true);
|
||||
|
||||
fn main() -> Result<(), std::io::Error> {
|
||||
colored::control::set_override(true);
|
||||
let init = init();
|
||||
let mut data = if let Err(status) = init {
|
||||
match status {
|
||||
args::Status::Exit => {
|
||||
return Ok(());
|
||||
}
|
||||
args::Status::Error => {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"Invalid input",
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
init.ok().unwrap()
|
||||
};
|
||||
|
||||
data.expand_command();
|
||||
use shell::Mode::*;
|
||||
match data.mode {
|
||||
Suggestion => modes::suggestion(&mut data),
|
||||
Cnf => modes::cnf(&mut data),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init() -> Result<shell::Data, args::Status> {
|
||||
let locale = {
|
||||
let sys_locale = get_locale().unwrap_or("en-US".to_string());
|
||||
if sys_locale.len() < 2 {
|
||||
"en-US".to_string()
|
||||
} else {
|
||||
sys_locale
|
||||
}
|
||||
};
|
||||
rust_i18n::set_locale(&locale[0..2]);
|
||||
|
||||
let status = args::handle_args();
|
||||
match status {
|
||||
args::Status::Exit => {
|
||||
return Err(status);
|
||||
}
|
||||
args::Status::Error => {
|
||||
return Err(status);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(shell::Data::init())
|
||||
}
|
||||
150
core/src/modes.rs
Normal file
150
core/src/modes.rs
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
use crate::shell::Data;
|
||||
use crate::suggestions::suggest_candidates;
|
||||
use crate::system;
|
||||
use crate::{shell, suggestions};
|
||||
use colored::Colorize;
|
||||
use inquire::*;
|
||||
use pay_respects_utils::evals::best_match_path;
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
pub fn suggestion(data: &mut Data) {
|
||||
let shell = data.shell.clone();
|
||||
let mut last_command;
|
||||
|
||||
loop {
|
||||
last_command = data.command.clone();
|
||||
suggest_candidates(data);
|
||||
if data.candidates.is_empty() {
|
||||
break;
|
||||
};
|
||||
|
||||
for candidate in &mut data.candidates {
|
||||
shell::shell_syntax(&shell, candidate);
|
||||
}
|
||||
|
||||
suggestions::select_candidate(data);
|
||||
|
||||
let execution = suggestions::confirm_suggestion(data);
|
||||
if execution.is_ok() {
|
||||
return;
|
||||
} else {
|
||||
data.update_command(&data.suggest.clone().unwrap());
|
||||
let msg = Some(
|
||||
execution
|
||||
.err()
|
||||
.unwrap()
|
||||
.split_whitespace()
|
||||
.collect::<Vec<&str>>()
|
||||
.join(" "),
|
||||
);
|
||||
data.update_error(msg);
|
||||
|
||||
let retry_message = format!("{}...", t!("retry"));
|
||||
|
||||
eprintln!("\n{}\n", retry_message.cyan().bold());
|
||||
}
|
||||
}
|
||||
eprintln!("{}: {}\n", t!("no-suggestion"), last_command.red());
|
||||
eprintln!(
|
||||
"{}\n{}",
|
||||
t!("contribute"),
|
||||
option_env!("CARGO_PKG_REPOSITORY").unwrap_or("https://github.com/iffse/pay-respects/")
|
||||
);
|
||||
}
|
||||
|
||||
pub fn cnf(data: &mut Data) {
|
||||
let shell = data.shell.clone();
|
||||
let mut split_command = data.split.clone();
|
||||
|
||||
let executable = split_command[0].as_str();
|
||||
let shell_msg = format!("{}:", shell);
|
||||
eprintln!(
|
||||
"{} {}: {}\n",
|
||||
shell_msg.bold().red(),
|
||||
t!("command-not-found"),
|
||||
executable
|
||||
);
|
||||
|
||||
let best_match = best_match_path(executable, &data.executables);
|
||||
if best_match.is_some() {
|
||||
let best_match = best_match.unwrap();
|
||||
split_command[0] = best_match;
|
||||
let suggest = split_command.join(" ");
|
||||
|
||||
data.candidates.push(suggest.clone());
|
||||
suggestions::select_candidate(data);
|
||||
|
||||
let status = suggestions::confirm_suggestion(data);
|
||||
if status.is_err() {
|
||||
data.update_command(&suggest);
|
||||
let msg = Some(
|
||||
status
|
||||
.err()
|
||||
.unwrap()
|
||||
.split_whitespace()
|
||||
.collect::<Vec<&str>>()
|
||||
.join(" "),
|
||||
);
|
||||
data.update_error(msg);
|
||||
suggestion(data);
|
||||
}
|
||||
} else {
|
||||
let package_manager = match system::get_package_manager(data) {
|
||||
Some(package_manager) => match package_manager.as_str() {
|
||||
"apt" => {
|
||||
let cnf_dirs = [
|
||||
"/usr/lib/",
|
||||
"/data/data/com.termux/files/usr/libexec/termux/",
|
||||
];
|
||||
let mut package_manager = package_manager;
|
||||
for bin_dir in &cnf_dirs {
|
||||
let bin = format!("{}{}", bin_dir, "command-not-found");
|
||||
if Path::new(&bin).exists() {
|
||||
package_manager = bin;
|
||||
}
|
||||
}
|
||||
package_manager
|
||||
}
|
||||
_ => package_manager,
|
||||
},
|
||||
None => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
eprintln!("package_manager: {}", package_manager);
|
||||
|
||||
let packages = match system::get_packages(data, &package_manager, executable) {
|
||||
Some(packages) => packages,
|
||||
None => {
|
||||
eprintln!("{}: {}", "pay-respects".red(), t!("package-not-found"));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
eprintln!("packages: {:?}", packages);
|
||||
|
||||
let style = ui::Styled::default();
|
||||
let render_config = ui::RenderConfig::default().with_prompt_prefix(style);
|
||||
let msg = format!("{}:", t!("install-package")).bold().blue();
|
||||
let confirm = format!("[{}]", t!("confirm-yes")).green();
|
||||
let hint = format!("{} {} {}", "[↑/↓]".blue(), confirm, "[Ctrl+C]".red());
|
||||
eprintln!("{}", msg);
|
||||
eprintln!("{}", hint);
|
||||
let package = Select::new("\n", packages)
|
||||
.with_vim_mode(true)
|
||||
.without_help_message()
|
||||
.with_render_config(render_config)
|
||||
.without_filtering()
|
||||
.prompt()
|
||||
.unwrap();
|
||||
|
||||
// retry after installing package
|
||||
if system::install_package(data, &package_manager, &package) {
|
||||
let _ = suggestions::run_suggestion(data, &data.command);
|
||||
}
|
||||
}
|
||||
}
|
||||
185
core/src/replaces.rs
Normal file
185
core/src/replaces.rs
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
use pay_respects_utils::evals::*;
|
||||
|
||||
fn tag(name: &str, x: i32) -> String {
|
||||
format!("{{{}{}}}", name, x)
|
||||
}
|
||||
|
||||
pub fn eval_placeholder(
|
||||
string: &str,
|
||||
start: &str,
|
||||
end: &str,
|
||||
) -> (std::ops::Range<usize>, std::ops::Range<usize>) {
|
||||
let start_index = string.find(start).unwrap();
|
||||
let end_index = string[start_index..].find(end).unwrap() + start_index + end.len();
|
||||
|
||||
let placeholder = start_index..end_index;
|
||||
|
||||
let args = start_index + start.len()..end_index - end.len();
|
||||
|
||||
(placeholder, args)
|
||||
}
|
||||
|
||||
pub fn opts(suggest: &mut String, last_command: &mut String, opt_list: &mut Vec<(String, String)>) {
|
||||
let mut replace_tag = 0;
|
||||
let tag_name = "opts";
|
||||
|
||||
while suggest.contains(" {{opt::") {
|
||||
let (placeholder, args) = eval_placeholder(suggest, " {{opt::", "}}");
|
||||
|
||||
let opt = &suggest[args.to_owned()];
|
||||
let regex = opt.trim();
|
||||
let current_tag = tag(tag_name, replace_tag);
|
||||
|
||||
opt_list.push((current_tag.clone(), opt_regex(regex, last_command)));
|
||||
suggest.replace_range(placeholder, ¤t_tag);
|
||||
|
||||
replace_tag += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cmd_reg(suggest: &mut String, last_command: &str) {
|
||||
while suggest.contains("{{cmd::") {
|
||||
let (placeholder, args) = eval_placeholder(suggest, "{{cmd::", "}}");
|
||||
|
||||
let regex = suggest[args.to_owned()].trim();
|
||||
|
||||
let command = cmd_regex(regex, last_command);
|
||||
suggest.replace_range(placeholder, &command)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn err(suggest: &mut String, error_msg: &str) {
|
||||
while suggest.contains("{{err::") {
|
||||
let (placeholder, args) = eval_placeholder(suggest, "{{err::", "}}");
|
||||
|
||||
let regex = suggest[args.to_owned()].trim();
|
||||
|
||||
let command = err_regex(regex, error_msg);
|
||||
suggest.replace_range(placeholder, &command)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn command(suggest: &mut String, split_command: &[String]) {
|
||||
while suggest.contains("{{command") {
|
||||
let (placeholder, args) = eval_placeholder(suggest, "{{command", "}}");
|
||||
|
||||
let range = suggest[args.to_owned()].trim_matches(|c| c == '[' || c == ']');
|
||||
if let Some((start, end)) = range.split_once(':') {
|
||||
let mut start_index = start.parse::<i32>().unwrap_or(0);
|
||||
if start_index < 0 {
|
||||
start_index += split_command.len() as i32;
|
||||
};
|
||||
let mut end_index;
|
||||
let parsed_end = end.parse::<i32>();
|
||||
if parsed_end.is_err() {
|
||||
end_index = split_command.len() as i32;
|
||||
} else {
|
||||
end_index = parsed_end.unwrap();
|
||||
if end_index < 0 {
|
||||
end_index += split_command.len() as i32 + 1;
|
||||
} else {
|
||||
end_index += 1;
|
||||
}
|
||||
};
|
||||
|
||||
let command = split_command[start_index as usize..end_index as usize].join(" ");
|
||||
|
||||
suggest.replace_range(placeholder, &command);
|
||||
} else {
|
||||
let range = range.parse::<usize>().unwrap_or(0);
|
||||
let command = &split_command[range];
|
||||
|
||||
suggest.replace_range(placeholder, command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn typo(suggest: &mut String, split_command: &[String], executables: &[String], shell: &str) {
|
||||
while suggest.contains("{{typo") {
|
||||
let (placeholder, args) = eval_placeholder(suggest, "{{typo", "}}");
|
||||
|
||||
let index = if suggest.contains('[') {
|
||||
let split = suggest[args.to_owned()]
|
||||
.split(&['[', ']'])
|
||||
.collect::<Vec<&str>>();
|
||||
let command_index = split[1];
|
||||
if !command_index.contains(':') {
|
||||
let command_index = command_index.parse::<i32>().unwrap();
|
||||
|
||||
let index = if command_index < 0 {
|
||||
split_command.len() as i32 + command_index
|
||||
} else {
|
||||
command_index
|
||||
};
|
||||
index as usize..index as usize + 1
|
||||
} else {
|
||||
let (start, end) = command_index.split_once(':').unwrap();
|
||||
let start = start.parse::<i32>().unwrap_or(0);
|
||||
let start_index = if start < 0 {
|
||||
split_command.len() as i32 + start
|
||||
} else {
|
||||
start
|
||||
};
|
||||
let end = end.parse::<i32>();
|
||||
let end_index = if end.is_err() {
|
||||
split_command.len() as i32
|
||||
} else {
|
||||
let end = end.unwrap();
|
||||
if end < 0 {
|
||||
split_command.len() as i32 + end + 1
|
||||
} else {
|
||||
end + 1
|
||||
}
|
||||
};
|
||||
|
||||
start_index as usize..end_index as usize
|
||||
}
|
||||
} else {
|
||||
unreachable!("Typo suggestion must have a command index");
|
||||
};
|
||||
|
||||
let match_list = if suggest.contains('(') {
|
||||
let split = suggest[args.to_owned()]
|
||||
.split_once("(")
|
||||
.unwrap()
|
||||
.1
|
||||
.rsplit_once(")")
|
||||
.unwrap()
|
||||
.0;
|
||||
split.split(',').collect::<Vec<&str>>()
|
||||
} else {
|
||||
unreachable!("Typo suggestion must have a match list");
|
||||
};
|
||||
|
||||
let match_list = match_list
|
||||
.iter()
|
||||
.map(|s| s.trim().to_string())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let command = if match_list[0].starts_with("{{shell") {
|
||||
let function = match_list.join(",");
|
||||
let (_, args) = eval_placeholder(&function, "{{shell", "}}");
|
||||
let function = &function[args.to_owned()].trim_matches(|c| c == '(' || c == ')');
|
||||
suggest_typo(
|
||||
&split_command[index],
|
||||
eval_shell_command(shell, function),
|
||||
executables,
|
||||
)
|
||||
} else {
|
||||
suggest_typo(&split_command[index], match_list, executables)
|
||||
};
|
||||
|
||||
suggest.replace_range(placeholder, &command);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shell(suggest: &mut String, shell: &str) {
|
||||
while suggest.contains("{{shell") {
|
||||
let (placeholder, args) = eval_placeholder(suggest, "{{shell", "}}");
|
||||
let range = suggest[args.to_owned()].trim_matches(|c| c == '(' || c == ')');
|
||||
|
||||
let command = eval_shell_command(shell, range);
|
||||
|
||||
suggest.replace_range(placeholder, &command.join("\n"));
|
||||
}
|
||||
}
|
||||
17
core/src/rules.rs
Normal file
17
core/src/rules.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
use crate::shell::Data;
|
||||
use pay_respects_parser::parse_rules;
|
||||
use pay_respects_utils::evals::*;
|
||||
|
||||
pub fn match_pattern(executable: &str, data: &Data) -> Option<Vec<String>> {
|
||||
let error_msg = &data.error;
|
||||
let shell = &data.shell;
|
||||
let last_command = &data.command;
|
||||
let executables = &data.executables;
|
||||
let mut candidates = vec![];
|
||||
parse_rules!("rules");
|
||||
if candidates.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(candidates)
|
||||
}
|
||||
}
|
||||
623
core/src/shell.rs
Normal file
623
core/src/shell.rs
Normal file
|
|
@ -0,0 +1,623 @@
|
|||
use pay_respects_utils::evals::split_command;
|
||||
use pay_respects_utils::files::get_path_files;
|
||||
use std::process::exit;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
pub const PRIVILEGE_LIST: [&str; 2] = ["sudo", "doas"];
|
||||
|
||||
pub enum Mode {
|
||||
Suggestion,
|
||||
Cnf,
|
||||
}
|
||||
pub struct Init {
|
||||
pub shell: String,
|
||||
pub binary_path: String,
|
||||
pub alias: String,
|
||||
pub auto_alias: bool,
|
||||
pub cnf: bool,
|
||||
}
|
||||
|
||||
impl Init {
|
||||
pub fn new() -> Init {
|
||||
Init {
|
||||
shell: String::from(""),
|
||||
binary_path: String::from(""),
|
||||
alias: String::from("f"),
|
||||
auto_alias: false,
|
||||
cnf: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Data {
|
||||
pub shell: String,
|
||||
pub command: String,
|
||||
pub suggest: Option<String>,
|
||||
pub candidates: Vec<String>,
|
||||
pub split: Vec<String>,
|
||||
pub alias: Option<HashMap<String, String>>,
|
||||
pub privilege: Option<String>,
|
||||
pub error: String,
|
||||
pub executables: Vec<String>,
|
||||
pub modules: Vec<String>,
|
||||
pub fallbacks: Vec<String>,
|
||||
pub mode: Mode,
|
||||
}
|
||||
|
||||
impl Data {
|
||||
pub fn init() -> Data {
|
||||
let shell = get_shell();
|
||||
let command = last_command(&shell).trim().to_string();
|
||||
let alias = alias_map(&shell);
|
||||
let mode = run_mode();
|
||||
let (executables, modules, fallbacks) = {
|
||||
let path_executables = get_path_files();
|
||||
let mut executables = vec![];
|
||||
let mut modules = vec![];
|
||||
let mut fallbacks = vec![];
|
||||
for exe in path_executables {
|
||||
if exe.starts_with("pay-respects-module-") {
|
||||
modules.push(exe.to_string());
|
||||
} else if exe.starts_with("pay-respects-fallback-") {
|
||||
fallbacks.push(exe.to_string());
|
||||
} else {
|
||||
executables.push(exe.to_string());
|
||||
}
|
||||
}
|
||||
if alias.is_some() {
|
||||
let alias = alias.as_ref().unwrap();
|
||||
for command in alias.keys() {
|
||||
if executables.contains(command) {
|
||||
continue;
|
||||
}
|
||||
executables.push(command.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
(executables, modules, fallbacks)
|
||||
};
|
||||
|
||||
let mut init = Data {
|
||||
shell,
|
||||
command,
|
||||
suggest: None,
|
||||
candidates: vec![],
|
||||
alias,
|
||||
split: vec![],
|
||||
privilege: None,
|
||||
error: "".to_string(),
|
||||
executables,
|
||||
modules,
|
||||
fallbacks,
|
||||
mode,
|
||||
};
|
||||
|
||||
init.split();
|
||||
init.update_error(None);
|
||||
init
|
||||
}
|
||||
|
||||
pub fn expand_command(&mut self) {
|
||||
if self.alias.is_none() {
|
||||
return;
|
||||
}
|
||||
let alias = self.alias.as_ref().unwrap();
|
||||
if let Some(command) = expand_alias_multiline(alias, &self.command) {
|
||||
#[cfg(debug_assertions)]
|
||||
eprintln!("expand_command: {}", command);
|
||||
self.update_command(&command);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expand_suggest(&mut self) {
|
||||
if self.alias.is_none() {
|
||||
return;
|
||||
}
|
||||
let alias = self.alias.as_ref().unwrap();
|
||||
if let Some(suggest) = expand_alias_multiline(alias, self.suggest.as_ref().unwrap()) {
|
||||
#[cfg(debug_assertions)]
|
||||
eprintln!("expand_suggest: {}", suggest);
|
||||
self.update_suggest(&suggest);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn split(&mut self) {
|
||||
let mut split = split_command(&self.command);
|
||||
if PRIVILEGE_LIST.contains(&split[0].as_str()) {
|
||||
self.command = self.command.replacen(&split[0], "", 1);
|
||||
self.privilege = Some(split.remove(0))
|
||||
}
|
||||
self.split = split;
|
||||
}
|
||||
|
||||
pub fn update_error(&mut self, error: Option<String>) {
|
||||
if let Some(error) = error {
|
||||
self.error = error;
|
||||
} else {
|
||||
self.error = get_error(&self.shell, &self.command);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_command(&mut self, command: &str) {
|
||||
self.command = command.to_string();
|
||||
self.split();
|
||||
}
|
||||
|
||||
pub fn update_suggest(&mut self, suggest: &str) {
|
||||
let split = split_command(suggest);
|
||||
if PRIVILEGE_LIST.contains(&split[0].as_str()) {
|
||||
self.suggest = Some(suggest.replacen(&split[0], "", 1));
|
||||
self.privilege = Some(split[0].clone())
|
||||
} else {
|
||||
self.suggest = Some(suggest.to_string());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn elevate(data: &mut Data, command: &mut String) {
|
||||
for privilege in PRIVILEGE_LIST.iter() {
|
||||
if data.executables.contains(&privilege.to_string()) {
|
||||
*command = format!("{} {}", privilege, command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_candidates_no_dup(
|
||||
command: &str,
|
||||
candidates: &mut Vec<String>,
|
||||
new_candidates: &Vec<String>,
|
||||
) {
|
||||
for candidate in new_candidates {
|
||||
let candidate = candidate.trim();
|
||||
if candidate != command && !candidates.contains(&candidate.to_string()) {
|
||||
candidates.push(candidate.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_error(shell: &str, command: &str) -> String {
|
||||
let error_msg = std::env::var("_PR_ERROR_MSG");
|
||||
let error = if let Ok(error_msg) = error_msg {
|
||||
std::env::remove_var("_PR_ERROR_MSG");
|
||||
error_msg
|
||||
} else {
|
||||
command_output_threaded(shell, command)
|
||||
};
|
||||
error.split_whitespace().collect::<Vec<&str>>().join(" ")
|
||||
}
|
||||
|
||||
pub fn command_output_threaded(shell: &str, command: &str) -> String {
|
||||
let (sender, receiver) = channel();
|
||||
|
||||
let _shell = shell.to_owned();
|
||||
let _command = command.to_owned();
|
||||
thread::spawn(move || {
|
||||
sender
|
||||
.send(
|
||||
std::process::Command::new(_shell)
|
||||
.arg("-c")
|
||||
.arg(_command)
|
||||
.env("LC_ALL", "C")
|
||||
.output()
|
||||
.expect("failed to execute process"),
|
||||
)
|
||||
.expect("failed to send output");
|
||||
});
|
||||
|
||||
match receiver.recv_timeout(Duration::from_secs(3)) {
|
||||
Ok(output) => match output.stderr.is_empty() {
|
||||
true => String::from_utf8_lossy(&output.stdout).to_lowercase(),
|
||||
false => String::from_utf8_lossy(&output.stderr).to_lowercase(),
|
||||
},
|
||||
Err(_) => {
|
||||
use colored::*;
|
||||
eprintln!("Timeout while executing command: {}", command.red());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn command_output(shell: &str, command: &str) -> String {
|
||||
let output = std::process::Command::new(shell)
|
||||
.arg("-c")
|
||||
.arg(command)
|
||||
.env("LC_ALL", "C")
|
||||
.output()
|
||||
.expect("failed to execute process");
|
||||
|
||||
match output.stdout.is_empty() {
|
||||
false => String::from_utf8_lossy(&output.stdout).to_lowercase(),
|
||||
true => String::from_utf8_lossy(&output.stderr).to_lowercase(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn module_output(data: &Data, module: &str) -> Option<Vec<String>> {
|
||||
let shell = &data.shell;
|
||||
let executable = &data.split[0];
|
||||
let last_command = &data.command;
|
||||
let error_msg = &data.error;
|
||||
let executables = data.executables.clone().join(",");
|
||||
let output = std::process::Command::new(shell)
|
||||
.arg("-c")
|
||||
.arg(module)
|
||||
.env("_PR_COMMAND", executable)
|
||||
.env("_PR_SHELL", shell)
|
||||
.env("_PR_LAST_COMMAND", last_command)
|
||||
.env("_PR_ERROR_MSG", error_msg)
|
||||
.env("_PR_EXECUTABLES", executables)
|
||||
.output()
|
||||
.expect("failed to execute process");
|
||||
|
||||
if !output.stderr.is_empty() {
|
||||
eprintln!("{}", String::from_utf8_lossy(&output.stderr));
|
||||
}
|
||||
|
||||
if output.stdout.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let break_holder = "<_PR_BR>";
|
||||
Some(
|
||||
String::from_utf8_lossy(&output.stdout)[..output.stdout.len() - break_holder.len()]
|
||||
.split("<_PR_BR>")
|
||||
.map(|s| s.trim().to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn last_command(shell: &str) -> String {
|
||||
let last_command = match std::env::var("_PR_LAST_COMMAND") {
|
||||
Ok(command) => command,
|
||||
Err(_) => {
|
||||
eprintln!(
|
||||
"{}",
|
||||
t!(
|
||||
"no-env-setup",
|
||||
var = "_PR_LAST_COMMAND",
|
||||
help = "pay-respects -h"
|
||||
)
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
match shell {
|
||||
"bash" => {
|
||||
let first_line = last_command.lines().next().unwrap().trim();
|
||||
first_line.split_once(' ').unwrap().1.to_string()
|
||||
}
|
||||
"zsh" => last_command,
|
||||
"fish" => last_command,
|
||||
"nu" => last_command,
|
||||
_ => last_command,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_mode() -> Mode {
|
||||
match std::env::var("_PR_MODE") {
|
||||
Ok(mode) => match mode.as_str() {
|
||||
"suggestion" => Mode::Suggestion,
|
||||
"cnf" => Mode::Cnf,
|
||||
_ => Mode::Suggestion,
|
||||
},
|
||||
Err(_) => Mode::Suggestion,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alias_map(shell: &str) -> Option<HashMap<String, String>> {
|
||||
let env = std::env::var("_PR_ALIAS");
|
||||
if env.is_err() {
|
||||
return None;
|
||||
}
|
||||
let env = env.unwrap();
|
||||
if env.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut alias_map = HashMap::new();
|
||||
match shell {
|
||||
"bash" => {
|
||||
for line in env.lines() {
|
||||
let alias = line.replace("alias ", "");
|
||||
let (alias, command) = alias.split_once('=').unwrap();
|
||||
let command = command.trim().trim_matches('\'');
|
||||
alias_map.insert(alias.to_string(), command.to_string());
|
||||
}
|
||||
}
|
||||
"zsh" => {
|
||||
for line in env.lines() {
|
||||
let (alias, command) = line.split_once('=').unwrap();
|
||||
let command = command.trim().trim_matches('\'');
|
||||
alias_map.insert(alias.to_string(), command.to_string());
|
||||
}
|
||||
}
|
||||
"fish" => {
|
||||
for line in env.lines() {
|
||||
let alias = line.replace("alias ", "");
|
||||
let (alias, command) = alias.split_once(' ').unwrap();
|
||||
let command = command.trim().trim_matches('\'');
|
||||
alias_map.insert(alias.to_string(), command.to_string());
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
unreachable!("Unsupported shell: {}", shell);
|
||||
}
|
||||
}
|
||||
Some(alias_map)
|
||||
}
|
||||
|
||||
pub fn expand_alias(map: &HashMap<String, String>, command: &str) -> Option<String> {
|
||||
let (command, args) = if let Some(split) = command.split_once(' ') {
|
||||
(split.0, split.1)
|
||||
} else {
|
||||
(command, "")
|
||||
};
|
||||
map.get(command)
|
||||
.map(|expand| format!("{} {}", expand, args))
|
||||
}
|
||||
|
||||
pub fn expand_alias_multiline(map: &HashMap<String, String>, command: &str) -> Option<String> {
|
||||
let lines = command.lines().collect::<Vec<&str>>();
|
||||
let mut expanded = String::new();
|
||||
let mut expansion = false;
|
||||
for line in lines {
|
||||
if let Some(expand) = expand_alias(map, line) {
|
||||
expanded = format!("{}\n{}", expanded, expand);
|
||||
expansion = true;
|
||||
} else {
|
||||
expanded = format!("{}\n{}", expanded, line);
|
||||
}
|
||||
}
|
||||
if expansion {
|
||||
Some(expanded)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initialization(init: &mut Init) {
|
||||
let last_command;
|
||||
let shell_alias;
|
||||
let alias = &init.alias;
|
||||
let auto_alias = init.auto_alias;
|
||||
let cnf = init.cnf;
|
||||
let binary_path = &init.binary_path;
|
||||
|
||||
match init.shell.as_str() {
|
||||
"bash" => {
|
||||
last_command = "$(history 2)";
|
||||
shell_alias = "$(alias)"
|
||||
}
|
||||
"zsh" => {
|
||||
last_command = "$(fc -ln -1)";
|
||||
shell_alias = "$(alias)"
|
||||
}
|
||||
"fish" => {
|
||||
last_command = "$(history | head -n 1)";
|
||||
shell_alias = "$(alias)";
|
||||
}
|
||||
"nu" | "nush" | "nushell" => {
|
||||
last_command = "(history | last).command";
|
||||
shell_alias = "\"\"";
|
||||
init.shell = "nu".to_string();
|
||||
}
|
||||
"pwsh" | "powershell" => {
|
||||
last_command = "Get-History | Select-Object -Last 1 | ForEach-Object {$_.CommandLine}";
|
||||
shell_alias = ";";
|
||||
init.shell = "pwsh".to_string();
|
||||
}
|
||||
_ => {
|
||||
println!("Unknown shell: {}", init.shell);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let shell = &init.shell;
|
||||
|
||||
if init.shell == "nu" {
|
||||
let init = format!(
|
||||
r#"
|
||||
def --env {} [] {{
|
||||
let dir = (with-env {{ _PR_LAST_COMMAND: {}, _PR_SHELL: nu }} {{ {} }})
|
||||
cd $dir
|
||||
}}
|
||||
"#,
|
||||
init.alias, last_command, init.binary_path
|
||||
);
|
||||
println!("{}", init);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut initialize = match shell.as_str() {
|
||||
"bash" | "zsh" | "fish" => format!(
|
||||
"\
|
||||
eval $(_PR_LAST_COMMAND=\"{}\" \
|
||||
_PR_ALIAS=\"{}\" \
|
||||
_PR_SHELL=\"{}\" \
|
||||
\"{}\")",
|
||||
last_command, shell_alias, shell, binary_path
|
||||
),
|
||||
"pwsh" | "powershell" => format!(
|
||||
r#"& {{
|
||||
try {{
|
||||
# fetch command and error from session history only when not in cnf mode
|
||||
if ($env:_PR_MODE -ne 'cnf') {{
|
||||
$env:_PR_LAST_COMMAND = ({});
|
||||
$err = Get-Error;
|
||||
if ($env:_PR_LAST_COMMAND -eq $err.InvocationInfo.Line) {{
|
||||
$env:_PR_ERROR_MSG = $err.Exception.Message
|
||||
}}
|
||||
}}
|
||||
$env:_PR_SHELL = '{}';
|
||||
&'{}';
|
||||
}}
|
||||
finally {{
|
||||
# restore mode from cnf
|
||||
if ($env:_PR_MODE -eq 'cnf') {{
|
||||
$env:_PR_MODE = $env:_PR_PWSH_ORIGIN_MODE;
|
||||
$env:_PR_PWSH_ORIGIN_MODE = $null;
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
"#,
|
||||
last_command, shell, binary_path
|
||||
),
|
||||
_ => {
|
||||
println!("Unsupported shell: {}", shell);
|
||||
return;
|
||||
}
|
||||
};
|
||||
if !auto_alias {
|
||||
println!("{}", initialize);
|
||||
return;
|
||||
}
|
||||
|
||||
match shell.as_str() {
|
||||
"bash" | "zsh" => {
|
||||
initialize = format!(r#"alias {}='{}'"#, alias, initialize);
|
||||
}
|
||||
"fish" => {
|
||||
initialize = format!(
|
||||
r#"
|
||||
function {} -d "Terminal command correction"
|
||||
eval $({})
|
||||
end
|
||||
"#,
|
||||
alias, initialize
|
||||
);
|
||||
}
|
||||
"pwsh" => {
|
||||
initialize = format!(
|
||||
"function {} {{\n{}",
|
||||
alias,
|
||||
initialize.split_once("\n").unwrap().1,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
println!("Unsupported shell: {}", shell);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if cnf {
|
||||
match shell.as_str() {
|
||||
"bash" => {
|
||||
initialize = format!(
|
||||
r#"
|
||||
command_not_found_handle() {{
|
||||
eval $(_PR_LAST_COMMAND="_ $@" _PR_SHELL="{}" _PR_MODE="cnf" "{}")
|
||||
}}
|
||||
|
||||
{}
|
||||
"#,
|
||||
shell, binary_path, initialize
|
||||
);
|
||||
}
|
||||
"zsh" => {
|
||||
initialize = format!(
|
||||
r#"
|
||||
command_not_found_handler() {{
|
||||
eval $(_PR_LAST_COMMAND="$@" _PR_SHELL="{}" _PR_MODE="cnf" "{}")
|
||||
}}
|
||||
|
||||
{}
|
||||
"#,
|
||||
shell, binary_path, initialize
|
||||
);
|
||||
}
|
||||
"fish" => {
|
||||
initialize = format!(
|
||||
r#"
|
||||
function fish_command_not_found --on-event fish_command_not_found
|
||||
eval $(_PR_LAST_COMMAND="$argv" _PR_SHELL="{}" _PR_MODE="cnf" "{}")
|
||||
end
|
||||
|
||||
{}
|
||||
"#,
|
||||
shell, binary_path, initialize
|
||||
);
|
||||
}
|
||||
"pwsh" => {
|
||||
initialize = format!(
|
||||
r#"{}
|
||||
$ExecutionContext.InvokeCommand.CommandNotFoundAction =
|
||||
{{
|
||||
param(
|
||||
[string]
|
||||
$commandName,
|
||||
[System.Management.Automation.CommandLookupEventArgs]
|
||||
$eventArgs
|
||||
)
|
||||
# powershell does not support run command with specific environment variables
|
||||
# but you must set global variables. so we are memorizing the current mode and the alias function will reset it later.
|
||||
$env:_PR_PWSH_ORIGIN_MODE=$env:_PR_MODE;
|
||||
$env:_PR_MODE='cnf';
|
||||
# powershell may search command with prefix 'get-' or '.\' first when this hook is hit, strip them
|
||||
$env:_PR_LAST_COMMAND=$commandName -replace '^get-|\.\\','';
|
||||
$eventArgs.Command = (Get-Command {});
|
||||
$eventArgs.StopSearch = $True;
|
||||
}}
|
||||
"#,
|
||||
initialize, alias
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
println!("Unsupported shell: {}", shell);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("{}", initialize);
|
||||
}
|
||||
|
||||
pub fn get_shell() -> String {
|
||||
match std::env::var("_PR_SHELL") {
|
||||
Ok(shell) => shell,
|
||||
Err(_) => {
|
||||
eprintln!(
|
||||
"{}",
|
||||
t!("no-env-setup", var = "_PR_SHELL", help = "pay-respects -h")
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shell_syntax(shell: &str, command: &mut String) {
|
||||
#[allow(clippy::single_match)]
|
||||
match shell {
|
||||
"nu" => {
|
||||
*command = command.replace(" && ", " and ");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shell_evaluated_commands(shell: &str, command: &str) -> Option<String> {
|
||||
let lines = command
|
||||
.lines()
|
||||
.map(|line| line.trim().trim_end_matches(['\\', ';', '|', '&']))
|
||||
.collect::<Vec<&str>>();
|
||||
let mut dirs = Vec::new();
|
||||
for line in lines {
|
||||
if let Some(dir) = line.strip_prefix("cd ") {
|
||||
dirs.push(dir.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
let cd_dir = dirs.join("");
|
||||
if cd_dir.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
#[allow(clippy::single_match)]
|
||||
match shell {
|
||||
"nu" => Some(cd_dir),
|
||||
_ => Some(format!("cd {}", cd_dir)),
|
||||
}
|
||||
}
|
||||
66
core/src/style.rs
Normal file
66
core/src/style.rs
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
use crate::shell::PRIVILEGE_LIST;
|
||||
use colored::*;
|
||||
use pay_respects_utils::evals::split_command;
|
||||
|
||||
// to_string() is necessary here, otherwise there won't be color in the output
|
||||
#[warn(clippy::unnecessary_to_owned)]
|
||||
pub fn highlight_difference(
|
||||
shell: &str,
|
||||
suggested_command: &str,
|
||||
last_command: &str,
|
||||
) -> Option<String> {
|
||||
// let replaced_newline = suggested_command.replace('\n', r" {{newline}} ");
|
||||
let mut split_suggested_command = split_command(suggested_command);
|
||||
let split_last_command = split_command(last_command);
|
||||
|
||||
if split_suggested_command == split_last_command {
|
||||
return None;
|
||||
}
|
||||
if split_suggested_command.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let privileged = PRIVILEGE_LIST.contains(&split_suggested_command[0].as_str());
|
||||
|
||||
let mut old_entries = Vec::new();
|
||||
for command in &split_suggested_command {
|
||||
if command.is_empty() {
|
||||
continue;
|
||||
}
|
||||
for old in split_last_command.clone() {
|
||||
if command == &old {
|
||||
old_entries.push(command.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// let mut highlighted = suggested_command.to_string();
|
||||
'next: for entry in split_suggested_command.iter_mut() {
|
||||
if entry == "\n" {
|
||||
continue;
|
||||
}
|
||||
for old in &old_entries {
|
||||
if old == entry {
|
||||
*entry = entry.blue().to_string();
|
||||
continue 'next;
|
||||
}
|
||||
}
|
||||
*entry = entry.red().bold().to_string();
|
||||
}
|
||||
|
||||
if privileged
|
||||
&& (suggested_command.contains("&&")
|
||||
|| suggested_command.contains("||")
|
||||
|| suggested_command.contains('>'))
|
||||
{
|
||||
split_suggested_command[1] =
|
||||
format!("{} -c \"", shell).red().bold().to_string() + &split_suggested_command[1];
|
||||
let len = split_suggested_command.len() - 1;
|
||||
split_suggested_command[len] =
|
||||
split_suggested_command[len].clone() + "\"".red().bold().to_string().as_str();
|
||||
}
|
||||
let highlighted = split_suggested_command.join(" ");
|
||||
|
||||
Some(highlighted.replace(" \n ", "\n"))
|
||||
}
|
||||
193
core/src/suggestions.rs
Normal file
193
core/src/suggestions.rs
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
use std::io::stderr;
|
||||
use std::process::{exit, Stdio};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use colored::Colorize;
|
||||
use inquire::*;
|
||||
|
||||
use crate::rules::match_pattern;
|
||||
use crate::shell::{add_candidates_no_dup, module_output, shell_evaluated_commands, Data};
|
||||
use crate::style::highlight_difference;
|
||||
|
||||
pub fn suggest_candidates(data: &mut Data) {
|
||||
let executable = &data.split[0];
|
||||
let command = &data.command;
|
||||
let privilege = &data.privilege;
|
||||
let mut suggest_candidates = vec![];
|
||||
|
||||
let modules = &data.modules;
|
||||
let fallbacks = &data.fallbacks;
|
||||
|
||||
if privilege.is_none() {
|
||||
if let Some(candidates) = match_pattern("_PR_privilege", data) {
|
||||
add_candidates_no_dup(command, &mut suggest_candidates, &candidates);
|
||||
}
|
||||
}
|
||||
if let Some(candidates) = match_pattern(executable, data) {
|
||||
add_candidates_no_dup(command, &mut suggest_candidates, &candidates);
|
||||
}
|
||||
if let Some(candidates) = match_pattern("_PR_general", data) {
|
||||
add_candidates_no_dup(command, &mut suggest_candidates, &candidates);
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
eprintln!("modules: {modules:?}");
|
||||
eprintln!("fallbacks: {fallbacks:?}");
|
||||
}
|
||||
|
||||
for module in modules {
|
||||
let new_candidates = module_output(data, module);
|
||||
|
||||
if let Some(candidates) = new_candidates {
|
||||
add_candidates_no_dup(command, &mut suggest_candidates, &candidates);
|
||||
}
|
||||
}
|
||||
|
||||
if !suggest_candidates.is_empty() {
|
||||
data.candidates = suggest_candidates;
|
||||
return;
|
||||
}
|
||||
for fallback in fallbacks {
|
||||
let candidates = module_output(data, fallback);
|
||||
eprintln!("fallback: {candidates:?}");
|
||||
if candidates.is_some() {
|
||||
add_candidates_no_dup(command, &mut suggest_candidates, &candidates.unwrap());
|
||||
data.candidates = suggest_candidates;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_candidate(data: &mut Data) {
|
||||
let candidates = &data.candidates;
|
||||
#[cfg(debug_assertions)]
|
||||
eprintln!("candidates: {candidates:?}");
|
||||
if candidates.len() == 1 {
|
||||
let suggestion = candidates[0].to_string();
|
||||
let highlighted = highlight_difference(&data.shell, &suggestion, &data.command).unwrap();
|
||||
eprintln!("{}\n", highlighted);
|
||||
let confirm = format!("[{}]", t!("confirm-yes")).green();
|
||||
eprintln!("{}: {} {}", t!("confirm"), confirm, "[Ctrl+C]".red());
|
||||
std::io::stdin().read_line(&mut String::new()).unwrap();
|
||||
data.update_suggest(&suggestion);
|
||||
data.expand_suggest();
|
||||
} else {
|
||||
let mut highlight_candidates = candidates
|
||||
.iter()
|
||||
.map(|candidate| highlight_difference(&data.shell, candidate, &data.command).unwrap())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
for candidate in highlight_candidates.iter_mut() {
|
||||
let lines = candidate.lines().collect::<Vec<&str>>();
|
||||
let mut formated = String::new();
|
||||
for (j, line) in lines.iter().enumerate() {
|
||||
if j == 0 {
|
||||
formated = line.to_string();
|
||||
} else {
|
||||
formated = format!("{}\n {}", formated, line);
|
||||
}
|
||||
}
|
||||
*candidate = formated;
|
||||
}
|
||||
|
||||
let style = ui::Styled::default();
|
||||
let render_config = ui::RenderConfig::default()
|
||||
.with_prompt_prefix(style)
|
||||
.with_answered_prompt_prefix(style)
|
||||
.with_highlighted_option_prefix(style);
|
||||
|
||||
let msg = format!("{}", t!("multi-suggest", num = candidates.len()))
|
||||
.bold()
|
||||
.blue();
|
||||
let confirm = format!("[{}]", t!("confirm-yes")).green();
|
||||
let hint = format!("{} {} {}", "[↑/↓]".blue(), confirm, "[Ctrl+C]".red());
|
||||
eprintln!("{}", msg);
|
||||
eprintln!("{}", hint);
|
||||
|
||||
let ans = Select::new("\n", highlight_candidates.clone())
|
||||
.with_page_size(1)
|
||||
.with_vim_mode(true)
|
||||
.without_filtering()
|
||||
.without_help_message()
|
||||
.with_render_config(render_config)
|
||||
.prompt()
|
||||
.unwrap();
|
||||
let pos = highlight_candidates.iter().position(|x| x == &ans).unwrap();
|
||||
let suggestion = candidates[pos].to_string();
|
||||
data.update_suggest(&suggestion);
|
||||
data.expand_suggest();
|
||||
}
|
||||
|
||||
data.candidates.clear();
|
||||
}
|
||||
|
||||
pub fn confirm_suggestion(data: &Data) -> Result<(), String> {
|
||||
let shell = &data.shell;
|
||||
let command = &data.suggest.clone().unwrap();
|
||||
#[cfg(debug_assertions)]
|
||||
eprintln!("running command: {command}");
|
||||
|
||||
let now = Instant::now();
|
||||
let process = run_suggestion(data, command);
|
||||
|
||||
if process.success() {
|
||||
let cd = shell_evaluated_commands(shell, command);
|
||||
if let Some(cd) = cd {
|
||||
println!("{}", cd);
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
if now.elapsed() > Duration::from_secs(3) {
|
||||
exit(1);
|
||||
}
|
||||
suggestion_err(data, command)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_suggestion(data: &Data, command: &str) -> std::process::ExitStatus {
|
||||
let shell = &data.shell;
|
||||
let privilege = &data.privilege;
|
||||
match privilege {
|
||||
Some(sudo) => std::process::Command::new(sudo)
|
||||
.arg(shell)
|
||||
.arg("-c")
|
||||
.arg(command)
|
||||
.stdout(stderr())
|
||||
.stderr(Stdio::inherit())
|
||||
.status()
|
||||
.expect("failed to execute process"),
|
||||
None => std::process::Command::new(shell)
|
||||
.arg("-c")
|
||||
.arg(command)
|
||||
.stdout(stderr())
|
||||
.stderr(Stdio::inherit())
|
||||
.status()
|
||||
.expect("failed to execute process"),
|
||||
}
|
||||
}
|
||||
|
||||
fn suggestion_err(data: &Data, command: &str) -> Result<(), String> {
|
||||
let shell = &data.shell;
|
||||
let privilege = &data.privilege;
|
||||
let process = match privilege {
|
||||
Some(sudo) => std::process::Command::new(sudo)
|
||||
.arg(shell)
|
||||
.arg("-c")
|
||||
.arg(command)
|
||||
.env("LC_ALL", "C")
|
||||
.output()
|
||||
.expect("failed to execute process"),
|
||||
None => std::process::Command::new(shell)
|
||||
.arg("-c")
|
||||
.arg(command)
|
||||
.env("LC_ALL", "C")
|
||||
.output()
|
||||
.expect("failed to execute process"),
|
||||
};
|
||||
let error_msg = match process.stderr.is_empty() {
|
||||
true => String::from_utf8_lossy(&process.stdout).to_lowercase(),
|
||||
false => String::from_utf8_lossy(&process.stderr).to_lowercase(),
|
||||
};
|
||||
Err(error_msg.to_string())
|
||||
}
|
||||
204
core/src/system.rs
Normal file
204
core/src/system.rs
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
use crate::shell::{command_output, elevate, Data};
|
||||
use colored::Colorize;
|
||||
use std::io::stderr;
|
||||
use std::process::Command;
|
||||
use std::process::Stdio;
|
||||
|
||||
pub fn get_package_manager(data: &mut Data) -> Option<String> {
|
||||
let package_managers = vec![
|
||||
"apt", "dnf", "emerge", "nix", "pacman", "yum",
|
||||
// "zypper",
|
||||
];
|
||||
|
||||
for package_manager in package_managers {
|
||||
if data.executables.contains(&package_manager.to_string()) {
|
||||
return Some(package_manager.to_string());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_packages(
|
||||
data: &mut Data,
|
||||
package_manager: &str,
|
||||
executable: &str,
|
||||
) -> Option<Vec<String>> {
|
||||
let shell = &data.shell.clone();
|
||||
match package_manager {
|
||||
"apt" => {
|
||||
if !data.executables.contains(&"apt-file".to_string()) {
|
||||
eprintln!(
|
||||
"{}: apt-file is required to find packages",
|
||||
"pay-respects".yellow()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
let result = command_output(
|
||||
shell,
|
||||
&format!("apt-file find --regexp '.*/bin/{}$'", executable),
|
||||
);
|
||||
if result.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let packages: Vec<String> = result
|
||||
.lines()
|
||||
.map(|line| line.split_once(':').unwrap().0.to_string())
|
||||
.collect();
|
||||
|
||||
if packages.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(packages)
|
||||
}
|
||||
}
|
||||
"dnf" | "yum" => {
|
||||
let result = command_output(
|
||||
shell,
|
||||
&format!("{} provides '/usr/bin/{}'", package_manager, executable),
|
||||
);
|
||||
if result.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let packages: Vec<String> = result
|
||||
.lines()
|
||||
.map(|line| line.split_whitespace().next().unwrap().to_string())
|
||||
.collect();
|
||||
if packages.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(packages)
|
||||
}
|
||||
}
|
||||
"emerge" => {
|
||||
if !data.executables.contains(&"e-file".to_string()) {
|
||||
eprintln!(
|
||||
"{}: pfl is required to find packages",
|
||||
"pay-respects".yellow()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
let result = command_output(shell, &format!("e-file /usr/bin/{}", executable));
|
||||
if result.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let mut packages = vec![];
|
||||
for line in result.lines() {
|
||||
if !line.starts_with(" ") {
|
||||
packages.push(line.to_string());
|
||||
}
|
||||
}
|
||||
if packages.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(packages)
|
||||
}
|
||||
}
|
||||
"nix" => {
|
||||
if !data.executables.contains(&"nix-locate".to_string()) {
|
||||
eprintln!(
|
||||
"{}: nix-index is required to find packages",
|
||||
"pay-respects".yellow()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
let result = command_output(shell, &format!("nix-locate 'bin/{}'", executable));
|
||||
if result.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let packages: Vec<String> = result
|
||||
.lines()
|
||||
.map(|line| {
|
||||
line.split_whitespace()
|
||||
.next()
|
||||
.unwrap()
|
||||
.rsplit_once('.')
|
||||
.unwrap()
|
||||
.0
|
||||
.to_string()
|
||||
})
|
||||
.collect();
|
||||
if packages.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(packages)
|
||||
}
|
||||
}
|
||||
"pacman" => {
|
||||
let result = if data.executables.contains(&"pkgfile".to_string()) {
|
||||
command_output(shell, &format!("pkgfile -b {}", executable))
|
||||
} else {
|
||||
command_output(shell, &format!("pacman -Fq /usr/bin/{}", executable))
|
||||
};
|
||||
if result.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let packages: Vec<String> = result
|
||||
.lines()
|
||||
.map(|line| line.split_whitespace().next().unwrap().to_string())
|
||||
.collect();
|
||||
if packages.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(packages)
|
||||
}
|
||||
}
|
||||
_ => match package_manager.ends_with("command-not-found") {
|
||||
true => {
|
||||
let result = command_output(shell, &format!("{} {}", package_manager, executable));
|
||||
if result.is_empty() {
|
||||
return None;
|
||||
}
|
||||
if result.contains("did you mean") || result.contains("is not installed") {
|
||||
let packages = result
|
||||
.lines()
|
||||
.skip(1)
|
||||
.map(|line| line.trim().to_string())
|
||||
.collect();
|
||||
return Some(packages);
|
||||
}
|
||||
None
|
||||
}
|
||||
false => unreachable!("Unsupported package manager"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn install_package(data: &mut Data, package_manager: &str, package: &str) -> bool {
|
||||
let shell = &data.shell.clone();
|
||||
let mut install = match package_manager {
|
||||
"apt" | "dnf" | "pkg" | "yum" | "zypper" => {
|
||||
format!("{} install {}", package_manager, package)
|
||||
}
|
||||
"emerge" => format!("emerge {}", package),
|
||||
"nix" => format!("nix profile install nixpkgs#{}", package),
|
||||
"pacman" => format!("pacman -S {}", package),
|
||||
_ => match package_manager.ends_with("command-not-found") {
|
||||
true => match package.starts_with("Command") {
|
||||
false => package.to_string(),
|
||||
true => {
|
||||
let split = package.split_whitespace().collect::<Vec<&str>>();
|
||||
let command = split[1];
|
||||
let package = split[split.len() - 1];
|
||||
let new_command = data.command.clone().replacen(command, package, 1);
|
||||
data.update_command(&new_command);
|
||||
format!("apt install {}", package)
|
||||
}
|
||||
},
|
||||
false => unreachable!("Unsupported package manager"),
|
||||
},
|
||||
};
|
||||
elevate(data, &mut install);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
eprintln!("install: {}", install);
|
||||
|
||||
let result = Command::new(shell)
|
||||
.arg("-c")
|
||||
.arg(install)
|
||||
.stdout(stderr())
|
||||
.stderr(Stdio::inherit())
|
||||
.status()
|
||||
.expect("failed to execute process");
|
||||
|
||||
result.success()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue