Add i18n by using rust i18n (#807)

* feat: initial i18n setup

* style: fmt

* feat: i18n support for new steps

* fix: build on Linux

* fix: build on Linux

* refactor: rm unused translation keys

---------

Co-authored-by: Steve Lau <stevelauc@outlook.com>
This commit is contained in:
Florian Nagel
2024-10-03 12:47:35 +02:00
committed by GitHub
parent c33d396489
commit 29c555c394
33 changed files with 1222 additions and 524 deletions

22
.github/workflows/check_locale_file.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
on:
pull_request:
push:
branches:
- main
name: Check i18n locale file
jobs:
check_locale:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install checker
# Build it with the dev profile as this is faster and the checker still works
run: |
cargo install --git https://github.com/topgrade-rs/topgrade_i18n_locale_file_checker --profile dev
- name: Run the checker
run: topgrade_i18n_locale_file_checker ./locales/app.yml

235
Cargo.lock generated
View File

@@ -90,6 +90,12 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "arc-swap"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
[[package]] [[package]]
name = "async-broadcast" name = "async-broadcast"
version = "0.7.1" version = "0.7.1"
@@ -319,6 +325,16 @@ dependencies = [
"piper", "piper",
] ]
[[package]]
name = "bstr"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706"
dependencies = [
"memchr",
"serde",
]
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.16.0" version = "3.16.0"
@@ -539,6 +555,25 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "crossbeam-deque"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.20" version = "0.8.20"
@@ -994,6 +1029,36 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "globset"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
dependencies = [
"aho-corasick",
"bstr",
"log",
"regex-automata 0.4.7",
"regex-syntax 0.8.4",
]
[[package]]
name = "globwalk"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc"
dependencies = [
"bitflags 1.3.2",
"ignore",
"walkdir",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.14.5" version = "0.14.5"
@@ -1276,12 +1341,38 @@ dependencies = [
"utf8_iter", "utf8_iter",
] ]
[[package]]
name = "ignore"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1"
dependencies = [
"crossbeam-deque",
"globset",
"log",
"memchr",
"regex-automata 0.4.7",
"same-file",
"walkdir",
"winapi-util",
]
[[package]] [[package]]
name = "indenter" name = "indenter"
version = "0.3.3" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.2.6" version = "2.2.6"
@@ -1289,7 +1380,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown 0.14.5",
] ]
[[package]] [[package]]
@@ -1363,6 +1454,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.14" version = "0.4.14"
@@ -1500,6 +1597,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "normpath"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5831952a9476f2fed74b77d74182fa5ddc4d21c72ec45a333b250e3ed0272804"
dependencies = [
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "notify-rust" name = "notify-rust"
version = "4.11.0" version = "4.11.0"
@@ -1611,7 +1717,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79"
dependencies = [ dependencies = [
"dlv-list", "dlv-list",
"hashbrown", "hashbrown 0.14.5",
] ]
[[package]] [[package]]
@@ -1982,6 +2088,57 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316"
[[package]]
name = "rust-i18n"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dcd94370631e5658a0a23635f7f47e43d06a00ad948e0bb5de79b00d85b880c"
dependencies = [
"globwalk",
"once_cell",
"regex",
"rust-i18n-macro",
"rust-i18n-support",
"smallvec",
]
[[package]]
name = "rust-i18n-macro"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "355763801dcf287e777e42def7c578410783477b804b1107852119e0b2518396"
dependencies = [
"glob",
"once_cell",
"proc-macro2",
"quote",
"rust-i18n-support",
"serde",
"serde_json",
"serde_yaml",
"syn 2.0.66",
]
[[package]]
name = "rust-i18n-support"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "399801f4d955abf1c3ce3ce2215dc76bd40beb4ae39e3a84936b21a79ce2caa5"
dependencies = [
"arc-swap",
"globwalk",
"lazy_static",
"normpath",
"once_cell",
"proc-macro2",
"regex",
"serde",
"serde_json",
"serde_yaml",
"toml 0.7.8",
"triomphe",
]
[[package]] [[package]]
name = "rust-ini" name = "rust-ini"
version = "0.21.0" version = "0.21.0"
@@ -2187,6 +2344,18 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_yaml"
version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b"
dependencies = [
"indexmap 1.9.3",
"ryu",
"serde",
"yaml-rust",
]
[[package]] [[package]]
name = "sha1" name = "sha1"
version = "0.10.6" version = "0.10.6"
@@ -2378,6 +2547,15 @@ dependencies = [
"syn 2.0.66", "syn 2.0.66",
] ]
[[package]]
name = "sys-locale"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e801cf239ecd6ccd71f03d270d67dd53d13e90aab208bf4b8fe4ad957ea949b0"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "tar" name = "tar"
version = "0.4.41" version = "0.4.41"
@@ -2520,6 +2698,18 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "toml"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit 0.19.15",
]
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.8.14" version = "0.8.14"
@@ -2541,13 +2731,26 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "toml_edit"
version = "0.19.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [
"indexmap 2.2.6",
"serde",
"serde_spanned",
"toml_datetime",
"winnow 0.5.40",
]
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.21.1" version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
dependencies = [ dependencies = [
"indexmap", "indexmap 2.2.6",
"toml_datetime", "toml_datetime",
"winnow 0.5.40", "winnow 0.5.40",
] ]
@@ -2558,7 +2761,7 @@ version = "0.22.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
dependencies = [ dependencies = [
"indexmap", "indexmap 2.2.6",
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
@@ -2588,6 +2791,7 @@ dependencies = [
"parselnk", "parselnk",
"regex", "regex",
"regex-split", "regex-split",
"rust-i18n",
"rust-ini", "rust-ini",
"self_update", "self_update",
"semver", "semver",
@@ -2595,10 +2799,11 @@ dependencies = [
"shell-words", "shell-words",
"shellexpand", "shellexpand",
"strum", "strum",
"sys-locale",
"tempfile", "tempfile",
"thiserror", "thiserror",
"tokio", "tokio",
"toml", "toml 0.8.14",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
"walkdir", "walkdir",
@@ -2713,6 +2918,17 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc"
[[package]]
name = "triomphe"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6631e42e10b40c0690bf92f404ebcfe6e1fdb480391d15f17cc8e96eeed5369"
dependencies = [
"arc-swap",
"serde",
"stable_deref_trait",
]
[[package]] [[package]]
name = "try-lock" name = "try-lock"
version = "0.2.5" version = "0.2.5"
@@ -3243,6 +3459,15 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]
[[package]] [[package]]
name = "yoke" name = "yoke"
version = "0.7.4" version = "0.7.4"

View File

@@ -52,6 +52,8 @@ merge = "~0.1"
regex-split = "~0.1" regex-split = "~0.1"
notify-rust = "~4.11" notify-rust = "~4.11"
wildmatch = "2.3.0" wildmatch = "2.3.0"
rust-i18n = "3.0.1"
sys-locale = "0.3.1"
[package.metadata.generate-rpm] [package.metadata.generate-rpm]
assets = [{ source = "target/release/topgrade", dest = "/usr/bin/topgrade" }] assets = [{ source = "target/release/topgrade", dest = "/usr/bin/topgrade" }]

303
locales/app.yml Normal file
View File

@@ -0,0 +1,303 @@
_version: 2
"Current system locale is {system_locale}":
en: "Current system locale is %{system_locale}"
"Dry running: {program_name} {arguments}":
en: "Dry running: %{program_name} %{arguments}"
"in {directory}":
en: "in %{directory}"
"Rebooting...":
en: "Rebooting..."
"Plugins upgraded":
en: "Plugins upgraded"
"Would self-update":
en: "Would self-update"
"Pulling":
en: "Pulling"
"No Breaking changes":
en: "No Breaking changes"
"Dropping you to shell. Fix what you need and then exit the shell.":
en: "Dropping you to shell. Fix what you need and then exit the shell."
"Topgrade launched in a new tmux session":
en: "Topgrade launched in a new tmux session"
#TODO: See if the \n breaks anything
"Topgrade upgraded to {version}:\n":
en: "Topgrade upgraded to %{version}:\n"
"Topgrade is up-to-date":
en: "Topgrade is up-to-date"
"Updating modules...":
en: "Updating modules..."
"Powershell Modules Update":
en: "Powershell Modules Update"
"Powershell is not installed":
en: "Powershell is not installed"
"Error detecting current distribution: {error}":
en: "Error detecting current distribution: %{error}"
"Error: {error}":
en: "Error: %{error}"
"Failed":
en: "Failed"
"pulling":
en: "pulling"
"Changed":
en: "Changed"
"Up-to-date":
en: "Up-to-date"
"Self update":
en: "Self update"
# The following 2 strings are used in the same sentence
"Only":
en: "Only"
"updated repositories will be shown...":
en: "updated repositories will be shown..."
"because it has no remotes":
en: "because it has no remotes"
"Skipping":
en: "Skipping"
"Aura(<0.4.6) requires sudo installed to work with AUR packages":
en: "Aura(<0.4.6) requires sudo installed to work with AUR packages"
"Pacman backup configuration files found:":
en: "Pacman backup configuration files found:"
"The package audit was successful, but vulnerable packages still remain on the system":
en: "The package audit was successful, but vulnerable packages still remain on the system"
"Syncing portage":
en: "Syncing portage"
"Finding available software":
en: "Finding available software"
"A system update is available. Do you wish to install it?":
en: "A system update is available. Do you wish to install it?"
"No new software available.":
en: "No new software available."
"No Xcode releases installed.":
en: "No Xcode releases installed."
"Would you like to move the former Xcode release to the trash?":
en: "Would you like to move the former Xcode release to the trash?"
"New Xcode release detected:":
en: "New Xcode release detected:"
"Would you like to install it?":
en: "Would you like to install it?"
"No global packages installed":
en: "No global packages installed"
"Remote Topgrade launched in Tmux":
en: "Remote Topgrade launched in Tmux"
"Remote Topgrade launched in an external terminal":
en: "Remote Topgrade launched in an external terminal"
"Collecting Vagrant boxes":
en: "Collecting Vagrant boxes"
"No Vagrant directories were specified in the configuration file":
en: "No Vagrant directories were specified in the configuration file"
"Vagrant boxes":
en: "Vagrant boxes"
"No outdated boxes":
en: "No outdated boxes"
"Summary":
en: "Summary"
"Topgrade finished with errors":
en: "Topgrade finished with errors"
"Topgrade finished successfully":
en: "Topgrade finished successfully"
"Topgrade {version_str} Breaking Changes":
en: "Topgrade %{version_str} Breaking Changes"
"Path {path} expanded to {expanded}":
en: "Path %{path} expanded to %{expanded}"
"Path {path} doesn't exist":
en: "Path %{path} doesn't exist"
"Cannot find {binary_name} in PATH":
en: "Cannot find %{binary_name} in PATH"
"Failed to get a UTF-8 encoded hostname":
en: "Failed to get a UTF-8 encoded hostname"
"Failed to get hostname: {err}":
en: "Failed to get hostname: %{err}"
"{python} is a Python 2, skip.":
en: "%{python} is a Python 2, skip."
"{python} is a Python shim, skip.":
en: "%{python} is a Python shim, skip."
"{key} failed:":
en: "%{key} failed:"
"{step_name} failed":
en: "%{step_name} failed"
"DragonFly BSD Packages":
en: "DragonFly BSD Packages"
"DragonFly BSD Audit":
en: "DragonFly BSD Audit"
"FreeBSD Update":
en: "FreeBSD Update"
"FreeBSD Packages":
en: "FreeBSD Packages"
"FreeBSD Audit":
en: "FreeBSD Audit"
"System update":
en: "System update"
"needrestart will be ran by the package manager":
en: "needrestart will be ran by the package manager"
"Check for needed restarts":
en: "Check for needed restarts"
"Should not run in WSL":
en: "Should not run in WSL"
"Firmware upgrades":
en: "Firmware upgrades"
"Flatpak System Packages":
en: "Flatpak System Packages"
"Snapd socket does not exist":
en: "Snapd socket does not exist"
"You need to specify at least one container":
en: "You need to specify at least one container"
"Skipped in --yes":
en: "Skipped in --yes"
"Configuration update":
en: "Configuration update"
"Going to execute `waydroid upgrade`, which would STOP the running container, is this ok?":
en: "Going to execute `waydroid upgrade`, which would STOP the running container, is this ok?"
"Skip the Waydroid step because the user don't want to proceed":
en: "Skip the Waydroid step because the user don't want to proceed"
"macOS App Store":
en: "macOS App Store"
"macOS system update":
en: "macOS system update"
"OpenBSD Update":
en: "OpenBSD Update"
"OpenBSD Packages":
en: "OpenBSD Packages"
"`fisher` is not defined in `fish`":
en: "`fisher` is not defined in `fish`"
"`fish_plugins` path doesn't exist: {err}":
en: "`fish_plugins` path doesn't exist: %{err}"
"`fish_update_completions` is not available":
en: "`fish_update_completions` is not available"
"Desktop doest not appear to be gnome":
en: "Desktop doest not appear to be gnome"
"Gnome shell extensions are unregistered in DBus":
en: "Gnome shell extensions are unregistered in DBus"
"Gnome Shell extensions":
en: "Gnome Shell extensions"
"Not a custom brew for macOS":
en: "Not a custom brew for macOS"
"Guix Pull Failed, Skipping":
en: "Guix Pull Failed, Skipping"
"Nix-darwin on macOS must be upgraded via darwin-rebuild switch":
en: "Nix-darwin on macOS must be upgraded via darwin-rebuild switch"
"`nix upgrade-nix` can only be used on macOS or non-NixOS Linux":
en: "`nix upgrade-nix` can only be used on macOS or non-NixOS Linux"
"`nix upgrade-nix` cannot be run when Nix is installed in a profile":
en: "`nix upgrade-nix` cannot be run when Nix is installed in a profile"
"Nix (self-upgrade)":
en: "Nix (self-upgrade)"
"Pyenv is installed, but $PYENV_ROOT is not set correctly":
en: "Pyenv is installed, but $PYENV_ROOT is not set correctly"
"pyenv is not a git repository":
en: "pyenv is not a git repository"
"Bun Packages":
en: "Bun Packages"
"WSL not installed":
en: "WSL not installed"
"Update WSL":
en: "Update WSL"
"Could not find Topgrade installed in WSL":
en: "Could not find Topgrade installed in WSL"
"Consider installing PSWindowsUpdate as the use of Windows Update via USOClient is not supported.":
en: "Consider installing PSWindowsUpdate as the use of Windows Update via USOClient is not supported."
"USOClient not supported.":
en: "USOClient not supported."
"Connecting to {hostname}...":
en: "Connecting to %{hostname}..."
"Skipping powered off box {vagrant_box}":
en: "Skipping powered off box %{vagrant_box}"
"`{repo_tag}` for `{platform}`":
en: "`%{repo_tag}` for `%{platform}`"
"Containers":
en: "Containers"
"Emacs directory does not exist":
en: "Emacs directory does not exist"
"Error getting the composer directory: {error}":
en: "Error getting the composer directory: %{error}"
"Composer directory {composer_home} isn't a descendant of the user's home directory":
en: "Composer directory %{composer_home} isn't a descendant of the user's home directory"
"Composer":
en: "Composer"
"Error running `dotnet tool list`. This is expected when a dotnet runtime is installed but no SDK.":
en: "Error running `dotnet tool list`. This is expected when a dotnet runtime is installed but no SDK."
"No dotnet global tools installed":
en: "No dotnet global tools installed"
"Racket Package Manager":
en: "Racket Package Manager"
"GH failed":
en: "GH failed"
"GitHub CLI Extensions":
en: "GitHub CLI Extensions"
"Julia Packages":
en: "Julia Packages"
"Update ClamAV Database(FreshClam)":
en: "Update ClamAV Database(FreshClam)"
"Path {pattern} did not contain any git repositories":
en: "Path %{pattern} did not contain any git repositories"
"No repositories to pull":
en: "No repositories to pull"
"Git repositories":
en: "Git repositories"
"Would pull {repo}":
en: "Would pull %{repo}"
"Node Package Manager":
en: "Node Package Manager"
"Performant Node Package Manager":
en: "Performant Node Package Manager"
"Yarn Package Manager":
en: "Yarn Package Manager"
"Deno installed outside of .deno directory":
en: "Deno installed outside of .deno directory"
"The Ultimate vimrc":
en: "The Ultimate vimrc"
"vim binary might be actually nvim":
en: "vim binary might be actually nvim"
"`{process}` failed: {exit_satus}":
en: "`%{process}` failed: %{exit_satus}"
"`{process}` failed: {exit_satus} with {output}":
en: "`%{process}` failed: %{exit_satus} with %{output}"
"Unknown Linux Distribution":
en: "Unknown Linux Distribution"
'File "/etc/os-release" does not exist or is empty':
en: 'File "/etc/os-release" does not exist or is empty'
"Failed getting the system package manager":
en: "Failed getting the system package manager"
"A step failed":
en: "A step failed"
"Dry running":
en: "Dry running"
"Topgrade Upgraded":
en: "Topgrade Upgraded"
"OK":
en: "OK"
"FAILED":
en: "FAILED"
"IGNORED":
en: "IGNORED"
"SKIPPED":
en: "SKIPPED"
# 'Y' and 'N' have to stay the same characters. Eg for German the translation
# would look sth like "(Y) Ja / (N) Nein"
"(Y)es/(N)o":
en: "(Y)es/(N)o"
# 'y', 'N', 's', 'q' have to stay the same throughout all translations.
# Eg German would look like "(y) Wiederholen / (N) Nein / (s) Konsole / (q) Beenden"
"Retry? (y)es/(N)o/(s)hell/(q)uit":
en: "Retry? (y)es/(N)o/(s)hell/(q)uit"
# 'R', 'S', 'Q' have to stay the same throughout all translations. Eg German would look like "\n(R) Neustarten\n(S) Konsole\n(Q) Beenden"
"\n(R)eboot\n(S)hell\n(Q)uit":
en: "\n(R)eboot\n(S)hell\n(Q)uit"
"Require sudo or counterpart but not found, skip":
en: "Require sudo or counterpart but not found, skip"
"sudo as user '{user}'":
en: "sudo as user '%{user}'"
"Updating aqua ...":
en: "Updating aqua ..."
"Updating aqua installed cli tools ...":
en: "Updating aqua installed cli tools ..."
"Updating Volta packages...":
en: "Updating Volta packages..."
"No packages installed with Volta":
en: "No packages installed with Volta"
"pyenv-update plugin is not installed":
en: "pyenv-update plugin is not installed"

View File

@@ -11,6 +11,7 @@ use crate::WINDOWS_DIRS;
use crate::XDG_DIRS; use crate::XDG_DIRS;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use etcetera::base_strategy::BaseStrategy; use etcetera::base_strategy::BaseStrategy;
use rust_i18n::t;
use std::{ use std::{
env::var, env::var,
fs::{read_to_string, OpenOptions}, fs::{read_to_string, OpenOptions},
@@ -45,7 +46,7 @@ impl FromStr for Version {
// They cannot be all 0s // They cannot be all 0s
assert!( assert!(
!(major == 0 && minor == 0 && patch == 0), !(major == 0 && minor == 0 && patch == 0),
"Version numbers can not be all 0s" "Version numbers cannot be all 0s"
); );
Ok(Self { Ok(Self {
@@ -118,12 +119,15 @@ pub(crate) fn first_run_of_major_release() -> Result<bool> {
/// Print breaking changes to the user. /// Print breaking changes to the user.
pub(crate) fn print_breaking_changes() { pub(crate) fn print_breaking_changes() {
let header = format!("Topgrade {VERSION_STR} Breaking Changes"); let header = format!(
"{}",
t!("Topgrade {version_str} Breaking Changes", version_str = VERSION_STR)
);
print_separator(header); print_separator(header);
let contents = if BREAKINGCHANGES.is_empty() { let contents = if BREAKINGCHANGES.is_empty() {
"No Breaking changes" t!("No Breaking changes").to_string()
} else { } else {
BREAKINGCHANGES BREAKINGCHANGES.to_string()
}; };
println!("{contents}\n"); println!("{contents}\n");
} }
@@ -159,7 +163,7 @@ mod test {
} }
#[test] #[test]
#[should_panic(expected = "Version numbers can not be all 0s")] #[should_panic(expected = "Version numbers cannot be all 0s")]
fn invalid_version() { fn invalid_version() {
let all_0 = "0.0.0"; let all_0 = "0.0.0";
all_0.parse::<Version>().unwrap(); all_0.parse::<Version>().unwrap();

View File

@@ -15,6 +15,7 @@ use etcetera::base_strategy::BaseStrategy;
use merge::Merge; use merge::Merge;
use regex::Regex; use regex::Regex;
use regex_split::RegexSplit; use regex_split::RegexSplit;
use rust_i18n::t;
use serde::Deserialize; use serde::Deserialize;
use strum::{EnumIter, EnumString, IntoEnumIterator, VariantNames}; use strum::{EnumIter, EnumString, IntoEnumIterator, VariantNames};
use which_crate::which; use which_crate::which;
@@ -25,6 +26,7 @@ use crate::sudo::SudoKind;
use crate::utils::string_prepend_str; use crate::utils::string_prepend_str;
use tracing::{debug, error}; use tracing::{debug, error};
// TODO: Add i18n to this. Tracking issue: https://github.com/topgrade-rs/topgrade/issues/859
pub static EXAMPLE_CONFIG: &str = include_str!("../config.example.toml"); pub static EXAMPLE_CONFIG: &str = include_str!("../config.example.toml");
/// Topgrade's default log level. /// Topgrade's default log level.
@@ -650,14 +652,14 @@ impl ConfigFile {
let include_contents = match fs::read_to_string(&include_path) { let include_contents = match fs::read_to_string(&include_path) {
Ok(c) => c, Ok(c) => c,
Err(e) => { Err(e) => {
error!("Unable to read {}: {}", include_path.display(), e); error!("Unable to read {}: {e}", include_path.display(),);
continue; continue;
} }
}; };
match toml::from_str::<Self>(&include_contents) { match toml::from_str::<Self>(&include_contents) {
Ok(include_parsed) => result.merge(include_parsed), Ok(include_parsed) => result.merge(include_parsed),
Err(e) => { Err(e) => {
error!("Failed to deserialize {}: {}", include_path.display(), e); error!("Failed to deserialize {}: {e}", include_path.display(),);
continue; continue;
} }
}; };
@@ -667,14 +669,17 @@ impl ConfigFile {
match toml::from_str::<Self>(contents) { match toml::from_str::<Self>(contents) {
Ok(contents) => result.merge(contents), Ok(contents) => result.merge(contents),
Err(e) => error!("Failed to deserialize {}: {}", config_path.display(), e), Err(e) => error!("Failed to deserialize {}: {e}", config_path.display(),),
} }
} }
if let Some(paths) = result.git.as_mut().and_then(|git| git.repos.as_mut()) { if let Some(paths) = result.git.as_mut().and_then(|git| git.repos.as_mut()) {
for path in paths.iter_mut() { for path in paths.iter_mut() {
let expanded = shellexpand::tilde::<&str>(&path.as_ref()).into_owned(); let expanded = shellexpand::tilde::<&str>(&path.as_ref()).into_owned();
debug!("Path {} expanded to {}", path, expanded); debug!(
"{}",
t!("Path {path} expanded to {expanded}", path = path, expanded = expanded)
);
*path = expanded; *path = expanded;
} }
} }
@@ -712,6 +717,8 @@ impl ConfigFile {
} }
// Command line arguments // Command line arguments
// TODO: i18n of clap currently not easily possible. Waiting for https://github.com/clap-rs/clap/issues/380
// Tracking issue for i18n: https://github.com/topgrade-rs/topgrade/issues/859
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(name = "topgrade", version)] #[command(name = "topgrade", version)]
pub struct CommandLineArgs { pub struct CommandLineArgs {
@@ -869,7 +876,7 @@ impl Config {
ConfigFile::read(opt.config.clone()).unwrap_or_else(|e| { ConfigFile::read(opt.config.clone()).unwrap_or_else(|e| {
// Inform the user about errors when loading the configuration, // Inform the user about errors when loading the configuration,
// but fallback to the default config to at least attempt to do something // but fallback to the default config to at least attempt to do something
error!("failed to load configuration: {}", e); error!("failed to load configuration: {e}");
ConfigFile::default() ConfigFile::default()
}) })
} else { } else {

View File

@@ -1,41 +1,98 @@
use std::process::ExitStatus; use std::{fmt::Display, process::ExitStatus};
use rust_i18n::t;
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug, PartialEq, Eq)] #[derive(Error, Debug, PartialEq, Eq)]
pub enum TopgradeError { pub enum TopgradeError {
#[error("`{0}` failed: {1}")]
ProcessFailed(String, ExitStatus), ProcessFailed(String, ExitStatus),
#[error("`{0}` failed: {1}")]
ProcessFailedWithOutput(String, ExitStatus, String), ProcessFailedWithOutput(String, ExitStatus, String),
#[error("Unknown Linux Distribution")]
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
UnknownLinuxDistribution, UnknownLinuxDistribution,
#[error("File \"/etc/os-release\" does not exist or is empty")]
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
EmptyOSReleaseFile, EmptyOSReleaseFile,
#[error("Failed getting the system package manager")]
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
FailedGettingPackageManager, FailedGettingPackageManager,
} }
impl Display for TopgradeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TopgradeError::ProcessFailed(process, exit_status) => {
write!(
f,
"{}",
t!(
"`{process}` failed: {exit_satus}",
process = process,
exit_status = exit_status
)
)
}
TopgradeError::ProcessFailedWithOutput(process, exit_status, output) => {
write!(
f,
"{}",
t!(
"`{process}` failed: {exit_satus} with {output}",
process = process,
exit_status = exit_status,
output = output
)
)
}
#[cfg(target_os = "linux")]
TopgradeError::UnknownLinuxDistribution => write!(f, "{}", t!("Unknown Linux Distribution")),
#[cfg(target_os = "linux")]
TopgradeError::EmptyOSReleaseFile => {
write!(f, "{}", t!("File \"/etc/os-release\" does not exist or is empty"))
}
#[cfg(target_os = "linux")]
TopgradeError::FailedGettingPackageManager => {
write!(f, "{}", t!("Failed getting the system package manager"))
}
}
}
}
#[derive(Error, Debug)] #[derive(Error, Debug)]
#[error("A step failed")]
pub struct StepFailed; pub struct StepFailed;
#[derive(Error, Debug)] impl Display for StepFailed {
#[error("Dry running")] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
pub struct DryRun(); write!(f, "{}", t!("A step failed"))
}
}
#[derive(Error, Debug)]
pub struct DryRun();
impl Display for DryRun {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", t!("Dry running"))
}
}
#[derive(Error, Debug)] #[derive(Error, Debug)]
#[error("{0}")]
pub struct SkipStep(pub String); pub struct SkipStep(pub String);
impl Display for SkipStep {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(all(windows, feature = "self-update"))] #[cfg(all(windows, feature = "self-update"))]
#[derive(Error, Debug)] #[derive(Error, Debug)]
#[error("Topgrade Upgraded")]
pub struct Upgraded(pub ExitStatus); pub struct Upgraded(pub ExitStatus);
#[cfg(all(windows, feature = "self-update"))]
impl Display for Upgraded {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", t!("Topgrade Upgraded"))
}
}

View File

@@ -1,7 +1,7 @@
#![allow(dead_code)] #![allow(dead_code)]
use crate::executor::RunType; use crate::executor::RunType;
use crate::sudo::Sudo; use crate::sudo::Sudo;
use crate::utils::{require_option, REQUIRE_SUDO}; use crate::utils::{get_require_sudo_string, require_option};
use crate::{config::Config, executor::Executor}; use crate::{config::Config, executor::Executor};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use std::env::var; use std::env::var;
@@ -33,7 +33,7 @@ impl<'a> ExecutionContext<'a> {
} }
pub fn execute_elevated(&self, command: &Path, interactive: bool) -> Result<Executor> { pub fn execute_elevated(&self, command: &Path, interactive: bool) -> Result<Executor> {
let sudo = require_option(self.sudo.as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(self.sudo.as_ref(), get_require_sudo_string())?;
Ok(sudo.execute_elevated(self, command, interactive)) Ok(sudo.execute_elevated(self, command, interactive))
} }

View File

@@ -4,6 +4,7 @@ use std::path::Path;
use std::process::{Child, Command, ExitStatus, Output}; use std::process::{Child, Command, ExitStatus, Output};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use rust_i18n::t;
use tracing::debug; use tracing::debug;
use crate::command::CommandExt; use crate::command::CommandExt;
@@ -209,17 +210,20 @@ pub struct DryCommand {
impl DryCommand { impl DryCommand {
fn dry_run(&self) { fn dry_run(&self) {
print!( print!(
"Dry running: {} {}", "{}",
self.program.to_string_lossy(), t!(
shell_words::join( "Dry running: {program_name} {arguments}",
self.args program_name = self.program.to_string_lossy(),
.iter() arguments = shell_words::join(
.map(|a| String::from(a.to_string_lossy())) self.args
.collect::<Vec<String>>() .iter()
.map(|a| String::from(a.to_string_lossy()))
.collect::<Vec<String>>()
)
) )
); );
match &self.directory { match &self.directory {
Some(dir) => println!(" in {}", dir.to_string_lossy()), Some(dir) => println!(" {}", t!("in {directory}", directory = dir.to_string_lossy())),
None => println!(), None => println!(),
}; };
} }

View File

@@ -18,6 +18,7 @@ use etcetera::base_strategy::Windows;
#[cfg(unix)] #[cfg(unix)]
use etcetera::base_strategy::Xdg; use etcetera::base_strategy::Xdg;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use rust_i18n::{i18n, t};
use tracing::debug; use tracing::debug;
use self::config::{CommandLineArgs, Config, Step}; use self::config::{CommandLineArgs, Config, Step};
@@ -54,6 +55,9 @@ pub(crate) static XDG_DIRS: Lazy<Xdg> = Lazy::new(|| Xdg::new().expect("No home
#[cfg(windows)] #[cfg(windows)]
pub(crate) static WINDOWS_DIRS: Lazy<Windows> = Lazy::new(|| Windows::new().expect("No home directory")); pub(crate) static WINDOWS_DIRS: Lazy<Windows> = Lazy::new(|| Windows::new().expect("No home directory"));
// Init and load the i18n files
i18n!("locales", fallback = "en");
fn run() -> Result<()> { fn run() -> Result<()> {
install_color_eyre()?; install_color_eyre()?;
ctrlc::set_handler(); ctrlc::set_handler();
@@ -72,6 +76,11 @@ fn run() -> Result<()> {
// and `Config::tracing_filter_directives()`. // and `Config::tracing_filter_directives()`.
let reload_handle = install_tracing(&opt.tracing_filter_directives())?; let reload_handle = install_tracing(&opt.tracing_filter_directives())?;
// Get current system locale and set it as the default locale
let system_locale = sys_locale::get_locale().unwrap_or("en".to_string());
rust_i18n::set_locale(&system_locale);
debug!("Current system locale is {system_locale}");
if let Some(shell) = opt.gen_completion { if let Some(shell) = opt.gen_completion {
let cmd = &mut CommandLineArgs::command(); let cmd = &mut CommandLineArgs::command();
clap_complete::generate(shell, cmd, clap::crate_name!(), &mut io::stdout()); clap_complete::generate(shell, cmd, clap::crate_name!(), &mut io::stdout());
@@ -210,7 +219,7 @@ fn run() -> Result<()> {
runner.execute(Step::System, "System update", || distribution.upgrade(&ctx))?; runner.execute(Step::System, "System update", || distribution.upgrade(&ctx))?;
} }
Err(e) => { Err(e) => {
println!("Error detecting current distribution: {e}"); println!("{}", t!("Error detecting current distribution: {error}", error = e));
} }
} }
runner.execute(Step::ConfigUpdate, "config-update", || linux::run_config_update(&ctx))?; runner.execute(Step::ConfigUpdate, "config-update", || linux::run_config_update(&ctx))?;
@@ -452,7 +461,7 @@ fn run() -> Result<()> {
runner.execute(Step::Vagrant, "Vagrant boxes", || vagrant::upgrade_vagrant_boxes(&ctx))?; runner.execute(Step::Vagrant, "Vagrant boxes", || vagrant::upgrade_vagrant_boxes(&ctx))?;
if !runner.report().data().is_empty() { if !runner.report().data().is_empty() {
print_separator("Summary"); print_separator(t!("Summary"));
for (key, result) in runner.report().data() { for (key, result) in runner.report().data() {
print_result(key, result); print_result(key, result);
@@ -476,7 +485,7 @@ fn run() -> Result<()> {
} }
if config.keep_at_end() { if config.keep_at_end() {
print_info("\n(R)eboot\n(S)hell\n(Q)uit"); print_info(t!("\n(R)eboot\n(S)hell\n(Q)uit"));
loop { loop {
match get_key() { match get_key() {
Ok(Key::Char('s')) | Ok(Key::Char('S')) => { Ok(Key::Char('s')) | Ok(Key::Char('S')) => {
@@ -498,10 +507,11 @@ fn run() -> Result<()> {
if !config.skip_notify() { if !config.skip_notify() {
notify_desktop( notify_desktop(
format!( if failed {
"Topgrade finished {}", t!("Topgrade finished with errors")
if failed { "with errors" } else { "successfully" } } else {
), t!("Topgrade finished successfully")
},
Some(Duration::from_secs(10)), Some(Duration::from_secs(10)),
) )
} }
@@ -536,7 +546,7 @@ fn main() {
// The `Debug` implementation of `eyre::Result` prints a multi-line // The `Debug` implementation of `eyre::Result` prints a multi-line
// error message that includes all the 'causes' added with // error message that includes all the 'causes' added with
// `.with_context(...)` calls. // `.with_context(...)` calls.
println!("Error: {error:?}"); println!("{}", t!("Error: {error}", error = format!("{:?}", error)));
} }
exit(1); exit(1);
} }

View File

@@ -5,6 +5,7 @@ use std::process::Command;
use crate::config::Step; use crate::config::Step;
use color_eyre::eyre::{bail, Result}; use color_eyre::eyre::{bail, Result};
use rust_i18n::t;
use self_update_crate::backends::github::Update; use self_update_crate::backends::github::Update;
use self_update_crate::update::UpdateStatus; use self_update_crate::update::UpdateStatus;
@@ -15,10 +16,10 @@ use crate::error::Upgraded;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
pub fn self_update(ctx: &ExecutionContext) -> Result<()> { pub fn self_update(ctx: &ExecutionContext) -> Result<()> {
print_separator("Self update"); print_separator(t!("Self update"));
if ctx.run_type().dry() { if ctx.run_type().dry() {
println!("Would self-update"); println!("{}", t!("Would self-update"));
Ok(()) Ok(())
} else { } else {
let assume_yes = ctx.config().yes(Step::SelfUpdate); let assume_yes = ctx.config().yes(Step::SelfUpdate);
@@ -38,17 +39,17 @@ pub fn self_update(ctx: &ExecutionContext) -> Result<()> {
.update_extended()?; .update_extended()?;
if let UpdateStatus::Updated(release) = &result { if let UpdateStatus::Updated(release) = &result {
println!("\nTopgrade upgraded to {}:\n", release.version); println!("{}", t!("Topgrade upgraded to {version}:\n", version = release.version));
if let Some(body) = &release.body { if let Some(body) = &release.body {
println!("{body}"); println!("{body}");
} }
} else { } else {
println!("Topgrade is up-to-date"); println!("{}", t!("Topgrade is up-to-date"));
} }
{ {
if result.updated() { if result.updated() {
print_info("Respawning..."); print_info(t!("Respawning..."));
let mut command = Command::new(current_exe?); let mut command = Command::new(current_exe?);
command.args(env::args().skip(1)).env("TOPGRADE_NO_SELF_UPGRADE", ""); command.args(env::args().skip(1)).env("TOPGRADE_NO_SELF_UPGRADE", "");

View File

@@ -1,187 +1,196 @@
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::path::Path; use std::path::Path;
use std::process::Command; use std::process::Command;
use color_eyre::eyre::eyre; use color_eyre::eyre::eyre;
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use tracing::{debug, error, warn}; use tracing::{debug, error, warn};
use wildmatch::WildMatch; use wildmatch::WildMatch;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::error::{self, TopgradeError}; use crate::error::{self, TopgradeError};
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::{execution_context::ExecutionContext, utils::require}; use crate::{execution_context::ExecutionContext, utils::require};
use rust_i18n::t;
// A string found in the output of docker for containers that weren't found in
// the docker registry. We use this to gracefully handle and skip containers // A string found in the output of docker for containers that weren't found in
// that cannot be pulled, likely because they don't exist in the registry in // the docker registry. We use this to gracefully handle and skip containers
// the first place. This happens e.g. when the user tags an image locally // that cannot be pulled, likely because they don't exist in the registry in
// themselves or when using docker-compose. // the first place. This happens e.g. when the user tags an image locally
const NONEXISTENT_REPO: &str = "repository does not exist"; // themselves or when using docker-compose.
const NONEXISTENT_REPO: &str = "repository does not exist";
/// Uniquely identifies a `Container`.
#[derive(Debug)] /// Uniquely identifies a `Container`.
struct Container { #[derive(Debug)]
/// `Repository` and `Tag` struct Container {
/// /// `Repository` and `Tag`
/// format: `Repository:Tag`, e.g., `nixos/nix:latest`. ///
repo_tag: String, /// format: `Repository:Tag`, e.g., `nixos/nix:latest`.
/// Platform repo_tag: String,
/// /// Platform
/// format: `OS/Architecture`, e.g., `linux/amd64`. ///
platform: String, /// format: `OS/Architecture`, e.g., `linux/amd64`.
} platform: String,
}
impl Container {
/// Construct a new `Container`. impl Container {
fn new(repo_tag: String, platform: String) -> Self { /// Construct a new `Container`.
Self { repo_tag, platform } fn new(repo_tag: String, platform: String) -> Self {
} Self { repo_tag, platform }
} }
}
impl Display for Container {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { impl Display for Container {
// e.g., "`fedora:latest` for `linux/amd64`" fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "`{}` for `{}`", self.repo_tag, self.platform) // e.g., "`fedora:latest` for `linux/amd64`"
} write!(
} f,
"{}",
/// Returns a Vector of all containers, with Strings in the format t!(
/// "REGISTRY/[PATH/]CONTAINER_NAME:TAG" "`{repo_tag}` for `{platform}`",
/// repo_tag = self.repo_tag,
/// Containers specified in `ignored_containers` will be filtered out. platform = self.platform
fn list_containers(crt: &Path, ignored_containers: Option<&Vec<String>>) -> Result<Vec<Container>> { )
let ignored_containers = ignored_containers.map(|patterns| { )
patterns }
.iter() }
.map(|pattern| WildMatch::new(pattern))
.collect::<Vec<WildMatch>>() /// Returns a Vector of all containers, with Strings in the format
}); /// "REGISTRY/[PATH/]CONTAINER_NAME:TAG"
///
debug!( /// Containers specified in `ignored_containers` will be filtered out.
"Querying '{} image ls --format \"{{{{.Repository}}}}:{{{{.Tag}}}}/{{{{.ID}}}}\"' for containers", fn list_containers(crt: &Path, ignored_containers: Option<&Vec<String>>) -> Result<Vec<Container>> {
crt.display() let ignored_containers = ignored_containers.map(|patterns| {
); patterns
let output = Command::new(crt) .iter()
.args(["image", "ls", "--format", "{{.Repository}}:{{.Tag}} {{.ID}}"]) .map(|pattern| WildMatch::new(pattern))
.output_checked_with_utf8(|_| Ok(()))?; .collect::<Vec<WildMatch>>()
});
let mut retval = vec![];
for line in output.stdout.lines() { debug!(
if line.starts_with("localhost") { "Querying '{} image ls --format \"{{{{.Repository}}}}:{{{{.Tag}}}}/{{{{.ID}}}}\"' for containers",
// Don't know how to update self-built containers crt.display()
debug!("Skipping self-built container '{}'", line); );
continue; let output = Command::new(crt)
} .args(["image", "ls", "--format", "{{.Repository}}:{{.Tag}} {{.ID}}"])
.output_checked_with_utf8(|_| Ok(()))?;
if line.contains("<none>") {
// Bogus/dangling container or intermediate layer let mut retval = vec![];
debug!("Skipping bogus container '{}'", line); for line in output.stdout.lines() {
continue; if line.starts_with("localhost") {
} // Don't know how to update self-built containers
debug!("Skipping self-built container '{}'", line);
if line.starts_with("vsc-") { continue;
debug!("Skipping visual studio code dev container '{}'", line); }
continue;
} if line.contains("<none>") {
// Bogus/dangling container or intermediate layer
debug!("Using container '{}'", line); debug!("Skipping bogus container '{}'", line);
continue;
// line is of format: `Repository:Tag ImageID`, e.g., `nixos/nix:latest d80fea9c32b4` }
let split_res = line.split(' ').collect::<Vec<&str>>();
assert_eq!(split_res.len(), 2); if line.starts_with("vsc-") {
let (repo_tag, image_id) = (split_res[0], split_res[1]); debug!("Skipping visual studio code dev container '{}'", line);
continue;
if let Some(ref ignored_containers) = ignored_containers { }
if ignored_containers.iter().any(|pattern| pattern.matches(repo_tag)) {
debug!("Skipping ignored container '{}'", line); debug!("Using container '{}'", line);
continue;
} // line is of format: `Repository:Tag ImageID`, e.g., `nixos/nix:latest d80fea9c32b4`
} let split_res = line.split(' ').collect::<Vec<&str>>();
assert_eq!(split_res.len(), 2);
debug!( let (repo_tag, image_id) = (split_res[0], split_res[1]);
"Querying '{} image inspect --format \"{{{{.Os}}}}/{{{{.Architecture}}}}\"' for container {}",
crt.display(), if let Some(ref ignored_containers) = ignored_containers {
image_id if ignored_containers.iter().any(|pattern| pattern.matches(repo_tag)) {
); debug!("Skipping ignored container '{}'", line);
let inspect_output = Command::new(crt) continue;
.args(["image", "inspect", image_id, "--format", "{{.Os}}/{{.Architecture}}"]) }
.output_checked_with_utf8(|_| Ok(()))?; }
let mut platform = inspect_output.stdout;
// truncate the tailing new line character debug!(
platform.truncate(platform.len() - 1); "Querying '{} image inspect --format \"{{{{.Os}}}}/{{{{.Architecture}}}}\"' for container {}",
assert!(platform.contains('/')); crt.display(),
image_id
retval.push(Container::new(repo_tag.to_string(), platform)); );
} let inspect_output = Command::new(crt)
.args(["image", "inspect", image_id, "--format", "{{.Os}}/{{.Architecture}}"])
Ok(retval) .output_checked_with_utf8(|_| Ok(()))?;
} let mut platform = inspect_output.stdout;
// truncate the tailing new line character
pub fn run_containers(ctx: &ExecutionContext) -> Result<()> { platform.truncate(platform.len() - 1);
// Check what runtime is specified in the config assert!(platform.contains('/'));
let container_runtime = ctx.config().containers_runtime().to_string();
let crt = require(container_runtime)?; retval.push(Container::new(repo_tag.to_string(), platform));
debug!("Using container runtime '{}'", crt.display()); }
print_separator("Containers"); Ok(retval)
let mut success = true; }
let containers =
list_containers(&crt, ctx.config().containers_ignored_tags()).context("Failed to list Docker containers")?; pub fn run_containers(ctx: &ExecutionContext) -> Result<()> {
debug!("Containers to inspect: {:?}", containers); // Check what runtime is specified in the config
let container_runtime = ctx.config().containers_runtime().to_string();
for container in containers.iter() { let crt = require(container_runtime)?;
debug!("Pulling container '{}'", container); debug!("Using container runtime '{}'", crt.display());
let args = vec![
"pull", print_separator(t!("Containers"));
container.repo_tag.as_str(), let mut success = true;
"--platform", let containers =
container.platform.as_str(), list_containers(&crt, ctx.config().containers_ignored_tags()).context("Failed to list Docker containers")?;
]; debug!("Containers to inspect: {:?}", containers);
let mut exec = ctx.run_type().execute(&crt);
for container in containers.iter() {
if let Err(e) = exec.args(&args).status_checked() { debug!("Pulling container '{}'", container);
error!("Pulling container '{}' failed: {}", container, e); let args = vec![
"pull",
// Find out if this is 'skippable' container.repo_tag.as_str(),
// This is necessary e.g. for docker, because unlike podman docker doesn't tell from "--platform",
// which repository a container originates (such as `docker.io`). This has the container.platform.as_str(),
// practical consequence that all containers, whether self-built, created by ];
// docker-compose or pulled from the docker hub, look exactly the same to us. We can let mut exec = ctx.run_type().execute(&crt);
// only find out what went wrong by manually parsing the output of the command...
if match exec.output_checked_utf8() { if let Err(e) = exec.args(&args).status_checked() {
Ok(s) => s.stdout.contains(NONEXISTENT_REPO) || s.stderr.contains(NONEXISTENT_REPO), error!("Pulling container '{}' failed: {}", container, e);
Err(e) => match e.downcast_ref::<TopgradeError>() {
Some(TopgradeError::ProcessFailedWithOutput(_, _, stderr)) => stderr.contains(NONEXISTENT_REPO), // Find out if this is 'skippable'
_ => false, // This is necessary e.g. for docker, because unlike podman docker doesn't tell from
}, // which repository a container originates (such as `docker.io`). This has the
} { // practical consequence that all containers, whether self-built, created by
warn!("Skipping unknown container '{}'", container); // docker-compose or pulled from the docker hub, look exactly the same to us. We can
continue; // only find out what went wrong by manually parsing the output of the command...
} if match exec.output_checked_utf8() {
Ok(s) => s.stdout.contains(NONEXISTENT_REPO) || s.stderr.contains(NONEXISTENT_REPO),
success = false; Err(e) => match e.downcast_ref::<TopgradeError>() {
} Some(TopgradeError::ProcessFailedWithOutput(_, _, stderr)) => stderr.contains(NONEXISTENT_REPO),
} _ => false,
},
if ctx.config().cleanup() { } {
// Remove dangling images warn!("Skipping unknown container '{}'", container);
debug!("Removing dangling images"); continue;
if let Err(e) = ctx }
.run_type()
.execute(&crt) success = false;
.args(["image", "prune", "-f"]) }
.status_checked() }
{
error!("Removing dangling images failed: {}", e); if ctx.config().cleanup() {
success = false; // Remove dangling images
} debug!("Removing dangling images");
} if let Err(e) = ctx
.run_type()
if success { .execute(&crt)
Ok(()) .args(["image", "prune", "-f"])
} else { .status_checked()
Err(eyre!(error::StepFailed)) {
} error!("Removing dangling images failed: {}", e);
} success = false;
}
}
if success {
Ok(())
} else {
Err(eyre!(error::StepFailed))
}
}

View File

@@ -4,6 +4,7 @@ use std::path::{Path, PathBuf};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use etcetera::base_strategy::BaseStrategy; use etcetera::base_strategy::BaseStrategy;
use rust_i18n::t;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
@@ -74,9 +75,12 @@ impl Emacs {
if let Some(doom) = &self.doom { if let Some(doom) = &self.doom {
Emacs::update_doom(doom, ctx)?; Emacs::update_doom(doom, ctx)?;
} }
let init_file = require_option(self.directory.as_ref(), String::from("Emacs directory does not exist"))? let init_file = require_option(
.join("init.el") self.directory.as_ref(),
.require()?; t!("Emacs directory does not exist").to_string(),
)?
.join("init.el")
.require()?;
print_separator("Emacs"); print_separator("Emacs");

View File

@@ -8,6 +8,7 @@ use std::{fs, io::Write};
use color_eyre::eyre::eyre; use color_eyre::eyre::eyre;
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use rust_i18n::t;
use semver::Version; use semver::Version;
use tempfile::tempfile_in; use tempfile::tempfile_in;
use tracing::{debug, error}; use tracing::{debug, error};
@@ -16,7 +17,7 @@ use crate::command::{CommandExt, Utf8Output};
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::executor::ExecutorOutput; use crate::executor::ExecutorOutput;
use crate::terminal::{print_separator, shell}; use crate::terminal::{print_separator, shell};
use crate::utils::{self, check_is_python_2_or_shim, require, require_option, which, PathExt, REQUIRE_SUDO}; use crate::utils::{self, check_is_python_2_or_shim, get_require_sudo_string, require, require_option, which, PathExt};
use crate::Step; use crate::Step;
use crate::HOME_DIR; use crate::HOME_DIR;
use crate::{ use crate::{
@@ -130,7 +131,7 @@ pub fn run_rubygems(ctx: &ExecutionContext) -> Result<()> {
.args(["update", "--system"]) .args(["update", "--system"])
.status_checked()?; .status_checked()?;
} else { } else {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
if !Path::new("/usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb").exists() { if !Path::new("/usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb").exists() {
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
@@ -159,7 +160,7 @@ pub fn run_haxelib_update(ctx: &ExecutionContext) -> Result<()> {
let mut command = if directory_writable { let mut command = if directory_writable {
ctx.run_type().execute(&haxelib) ctx.run_type().execute(&haxelib)
} else { } else {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let mut c = ctx.run_type().execute(sudo); let mut c = ctx.run_type().execute(sudo);
c.arg(&haxelib); c.arg(&haxelib);
c c
@@ -229,8 +230,8 @@ pub fn run_aqua(ctx: &ExecutionContext) -> Result<()> {
print_separator("Aqua"); print_separator("Aqua");
if ctx.run_type().dry() { if ctx.run_type().dry() {
println!("Updating aqua ..."); println!("{}", t!("Updating aqua ..."));
println!("Updating aqua installed cli tools ..."); println!("{}", t!("Updating aqua installed cli tools ..."));
Ok(()) Ok(())
} else { } else {
ctx.run_type().execute(&aqua).arg("update-aqua").status_checked()?; ctx.run_type().execute(&aqua).arg("update-aqua").status_checked()?;
@@ -364,7 +365,7 @@ pub fn run_vcpkg_update(ctx: &ExecutionContext) -> Result<()> {
let mut command = if is_root_install { let mut command = if is_root_install {
ctx.run_type().execute(&vcpkg) ctx.run_type().execute(&vcpkg)
} else { } else {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let mut c = ctx.run_type().execute(sudo); let mut c = ctx.run_type().execute(sudo);
c.arg(&vcpkg); c.arg(&vcpkg);
c c
@@ -665,7 +666,7 @@ pub fn run_tlmgr_update(ctx: &ExecutionContext) -> Result<()> {
let mut command = if directory_writable { let mut command = if directory_writable {
ctx.run_type().execute(&tlmgr) ctx.run_type().execute(&tlmgr)
} else { } else {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let mut c = ctx.run_type().execute(sudo); let mut c = ctx.run_type().execute(sudo);
c.arg(&tlmgr); c.arg(&tlmgr);
c c
@@ -722,19 +723,22 @@ pub fn run_composer_update(ctx: &ExecutionContext) -> Result<()> {
let composer_home = Command::new(&composer) let composer_home = Command::new(&composer)
.args(["global", "config", "--absolute", "--quiet", "home"]) .args(["global", "config", "--absolute", "--quiet", "home"])
.output_checked_utf8() .output_checked_utf8()
.map_err(|e| (SkipStep(format!("Error getting the composer directory: {e}")))) .map_err(|e| (SkipStep(t!("Error getting the composer directory: {error}", error = e).to_string())))
.map(|s| PathBuf::from(s.stdout.trim()))? .map(|s| PathBuf::from(s.stdout.trim()))?
.require()?; .require()?;
if !composer_home.is_descendant_of(&HOME_DIR) { if !composer_home.is_descendant_of(&HOME_DIR) {
return Err(SkipStep(format!( return Err(SkipStep(
"Composer directory {} isn't a descendant of the user's home directory", t!(
composer_home.display() "Composer directory {composer_home} isn't a descendant of the user's home directory",
)) composer_home = composer_home.display()
)
.to_string(),
)
.into()); .into());
} }
print_separator("Composer"); print_separator(t!("Composer"));
if ctx.config().composer_self_update() { if ctx.config().composer_self_update() {
cfg_if::cfg_if! { cfg_if::cfg_if! {
@@ -746,7 +750,7 @@ pub fn run_composer_update(ctx: &ExecutionContext) -> Result<()> {
}; };
if has_update { if has_update {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
.arg(&composer) .arg(&composer)
@@ -790,9 +794,10 @@ pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> {
{ {
Ok(output) => output, Ok(output) => output,
Err(_) => { Err(_) => {
return Err(SkipStep(String::from( return Err(SkipStep(
"Error running `dotnet tool list`. This is expected when a dotnet runtime is installed but no SDK.", t!("Error running `dotnet tool list`. This is expected when a dotnet runtime is installed but no SDK.")
)) .to_string(),
)
.into()); .into());
} }
}; };
@@ -820,7 +825,7 @@ pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> {
.peekable(); .peekable();
if packages.peek().is_none() { if packages.peek().is_none() {
return Err(SkipStep(String::from("No dotnet global tools installed")).into()); return Err(SkipStep(t!("No dotnet global tools installed").to_string()).into());
} }
print_separator(".NET"); print_separator(".NET");
@@ -831,7 +836,7 @@ pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> {
.execute(&dotnet) .execute(&dotnet)
.args(["tool", "update", package_name, "--global"]) .args(["tool", "update", package_name, "--global"])
.status_checked() .status_checked()
.with_context(|| format!("Failed to update .NET package {package_name}"))?; .with_context(|| format!("Failed to update .NET package {:?}", package_name))?;
} }
Ok(()) Ok(())
@@ -860,7 +865,7 @@ pub fn run_helix_grammars(ctx: &ExecutionContext) -> Result<()> {
pub fn run_raco_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_raco_update(ctx: &ExecutionContext) -> Result<()> {
let raco = require("raco")?; let raco = require("raco")?;
print_separator("Racket Package Manager"); print_separator(t!("Racket Package Manager"));
ctx.run_type() ctx.run_type()
.execute(raco) .execute(raco)
@@ -888,10 +893,10 @@ pub fn run_ghcli_extensions_upgrade(ctx: &ExecutionContext) -> Result<()> {
let result = Command::new(&gh).args(["extensions", "list"]).output_checked_utf8(); let result = Command::new(&gh).args(["extensions", "list"]).output_checked_utf8();
if result.is_err() { if result.is_err() {
debug!("GH result {:?}", result); debug!("GH result {:?}", result);
return Err(SkipStep(String::from("GH failed")).into()); return Err(SkipStep(t!("GH failed").to_string()).into());
} }
print_separator("GitHub CLI Extensions"); print_separator(t!("GitHub CLI Extensions"));
ctx.run_type() ctx.run_type()
.execute(&gh) .execute(&gh)
.args(["extension", "upgrade", "--all"]) .args(["extension", "upgrade", "--all"])
@@ -901,7 +906,7 @@ pub fn run_ghcli_extensions_upgrade(ctx: &ExecutionContext) -> Result<()> {
pub fn update_julia_packages(ctx: &ExecutionContext) -> Result<()> { pub fn update_julia_packages(ctx: &ExecutionContext) -> Result<()> {
let julia = require("julia")?; let julia = require("julia")?;
print_separator("Julia Packages"); print_separator(t!("Julia Packages"));
ctx.run_type() ctx.run_type()
.execute(julia) .execute(julia)
@@ -918,7 +923,7 @@ pub fn run_helm_repo_update(ctx: &ExecutionContext) -> Result<()> {
let mut success = true; let mut success = true;
let mut exec = ctx.run_type().execute(helm); let mut exec = ctx.run_type().execute(helm);
if let Err(e) = exec.arg("repo").arg("update").status_checked() { if let Err(e) = exec.arg("repo").arg("update").status_checked() {
error!("Updating repositories failed: {}", e); error!("Updating repositories failed: {e}");
success = match exec.output_checked_utf8() { success = match exec.output_checked_utf8() {
Ok(s) => s.stdout.contains(no_repo) || s.stderr.contains(no_repo), Ok(s) => s.stdout.contains(no_repo) || s.stderr.contains(no_repo),
Err(e) => match e.downcast_ref::<TopgradeError>() { Err(e) => match e.downcast_ref::<TopgradeError>() {
@@ -951,7 +956,7 @@ pub fn run_bob(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn run_certbot(ctx: &ExecutionContext) -> Result<()> { pub fn run_certbot(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let certbot = require("certbot")?; let certbot = require("certbot")?;
print_separator("Certbot"); print_separator("Certbot");
@@ -968,7 +973,7 @@ pub fn run_certbot(ctx: &ExecutionContext) -> Result<()> {
/// doc: https://docs.clamav.net/manual/Usage/SignatureManagement.html#freshclam /// doc: https://docs.clamav.net/manual/Usage/SignatureManagement.html#freshclam
pub fn run_freshclam(ctx: &ExecutionContext) -> Result<()> { pub fn run_freshclam(ctx: &ExecutionContext) -> Result<()> {
let freshclam = require("freshclam")?; let freshclam = require("freshclam")?;
print_separator("Update ClamAV Database(FreshClam)"); print_separator(t!("Update ClamAV Database(FreshClam)"));
ctx.run_type().execute(freshclam).status_checked() ctx.run_type().execute(freshclam).status_checked()
} }
@@ -1001,7 +1006,7 @@ pub fn run_lensfun_update_data(ctx: &ExecutionContext) -> Result<()> {
const EXIT_CODE_WHEN_NO_UPDATE: i32 = 1; const EXIT_CODE_WHEN_NO_UPDATE: i32 = 1;
if ctx.config().lensfun_use_sudo() { if ctx.config().lensfun_use_sudo() {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
print_separator(SEPARATOR); print_separator(SEPARATOR);
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)

View File

@@ -20,6 +20,7 @@ use crate::terminal::print_separator;
use crate::utils::{require, PathExt}; use crate::utils::{require, PathExt};
use crate::{error::SkipStep, terminal::print_warning, HOME_DIR}; use crate::{error::SkipStep, terminal::print_warning, HOME_DIR};
use etcetera::base_strategy::BaseStrategy; use etcetera::base_strategy::BaseStrategy;
use rust_i18n::t;
#[cfg(unix)] #[cfg(unix)]
use crate::XDG_DIRS; use crate::XDG_DIRS;
@@ -100,16 +101,18 @@ pub fn run_git_pull(ctx: &ExecutionContext) -> Result<()> {
// NOTE: this should be executed **before** skipping the Git step or the // NOTE: this should be executed **before** skipping the Git step or the
// user won't receive this warning in the cases where all the paths configured // user won't receive this warning in the cases where all the paths configured
// are bad patterns. // are bad patterns.
repos repos.bad_patterns.iter().for_each(|pattern| {
.bad_patterns print_warning(t!(
.iter() "Path {pattern} did not contain any git repositories",
.for_each(|pattern| print_warning(format!("Path {pattern} did not contain any git repositories"))); pattern = pattern
))
});
if repos.is_repos_empty() { if repos.is_repos_empty() {
return Err(SkipStep(String::from("No repositories to pull")).into()); return Err(SkipStep(t!("No repositories to pull").to_string()).into());
} }
print_separator("Git repositories"); print_separator(t!("Git repositories"));
repos.pull_repos(ctx) repos.pull_repos(ctx)
} }
@@ -143,7 +146,7 @@ fn get_head_revision<P: AsRef<Path>>(git: &Path, repo: P) -> Option<String> {
.output_checked_utf8() .output_checked_utf8()
.map(|output| output.stdout.trim().to_string()) .map(|output| output.stdout.trim().to_string())
.map_err(|e| { .map_err(|e| {
error!("Error getting revision for {}: {}", repo.as_ref().display(), e); error!("Error getting revision for {}: {e}", repo.as_ref().display(),);
e e
}) })
@@ -206,7 +209,7 @@ impl RepoStep {
} }
Err(e) => match e.kind() { Err(e) => match e.kind() {
io::ErrorKind::NotFound => debug!("{} does not exist", path.as_ref().display()), io::ErrorKind::NotFound => debug!("{} does not exist", path.as_ref().display()),
_ => error!("Error looking for {}: {}", path.as_ref().display(), e), _ => error!("Error looking for {}: {e}", path.as_ref().display(),),
}, },
} }
@@ -236,7 +239,7 @@ impl RepoStep {
res.map(|output| output.stdout.lines().count() > 0) res.map(|output| output.stdout.lines().count() > 0)
.map_err(|e| { .map_err(|e| {
error!("Error getting remotes for {}: {}", repo.as_ref().display(), e); error!("Error getting remotes for {}: {e}", repo.as_ref().display());
e e
}) })
.ok() .ok()
@@ -264,7 +267,7 @@ impl RepoStep {
} }
} }
Err(e) => { Err(e) => {
error!("Error in path {}", e); error!("Error in path {e}");
} }
} }
} }
@@ -273,7 +276,7 @@ impl RepoStep {
self.bad_patterns.push(String::from(pattern)); self.bad_patterns.push(String::from(pattern));
} }
} else { } else {
error!("Bad glob pattern: {}", pattern); error!("Bad glob pattern: {pattern}");
} }
} }
@@ -296,7 +299,7 @@ impl RepoStep {
let before_revision = get_head_revision(&self.git, &repo); let before_revision = get_head_revision(&self.git, &repo);
if ctx.config().verbose() { if ctx.config().verbose() {
println!("{} {}", style("Pulling").cyan().bold(), repo.as_ref().display()); println!("{} {}", style(t!("Pulling")).cyan().bold(), repo.as_ref().display());
} }
let mut command = AsyncCommand::new(&self.git); let mut command = AsyncCommand::new(&self.git);
@@ -322,13 +325,18 @@ impl RepoStep {
.wrap_err_with(|| format!("Failed to pull {}", repo.as_ref().display())); .wrap_err_with(|| format!("Failed to pull {}", repo.as_ref().display()));
if result.is_err() { if result.is_err() {
println!("{} pulling {}", style("Failed").red().bold(), repo.as_ref().display()); println!(
"{} {} {}",
style(t!("Failed")).red().bold(),
t!("pulling"),
repo.as_ref().display()
);
} else { } else {
let after_revision = get_head_revision(&self.git, repo.as_ref()); let after_revision = get_head_revision(&self.git, repo.as_ref());
match (&before_revision, &after_revision) { match (&before_revision, &after_revision) {
(Some(before), Some(after)) if before != after => { (Some(before), Some(after)) if before != after => {
println!("{} {}", style("Changed").yellow().bold(), repo.as_ref().display()); println!("{} {}", style(t!("Changed")).yellow().bold(), repo.as_ref().display());
Command::new(&self.git) Command::new(&self.git)
.stdin(Stdio::null()) .stdin(Stdio::null())
@@ -345,7 +353,7 @@ impl RepoStep {
} }
_ => { _ => {
if ctx.config().verbose() { if ctx.config().verbose() {
println!("{} {}", style("Up-to-date").green().bold(), repo.as_ref().display()); println!("{} {}", style(t!("Up-to-date")).green().bold(), repo.as_ref().display());
} }
} }
} }
@@ -363,15 +371,16 @@ impl RepoStep {
if ctx.run_type().dry() { if ctx.run_type().dry() {
self.repos self.repos
.iter() .iter()
.for_each(|repo| println!("Would pull {}", repo.display())); .for_each(|repo| println!("{}", t!("Would pull {repo}", repo = repo.display())));
return Ok(()); return Ok(());
} }
if !ctx.config().verbose() { if !ctx.config().verbose() {
println!( println!(
"\n{} updated repositories will be shown...\n", "\n{} {}\n",
style("Only").green().bold() style(t!("Only")).green().bold(),
t!("updated repositories will be shown...")
); );
} }
@@ -381,9 +390,10 @@ impl RepoStep {
.filter(|repo| match self.has_remotes(repo) { .filter(|repo| match self.has_remotes(repo) {
Some(false) => { Some(false) => {
println!( println!(
"{} {} because it has no remotes", "{} {} {}",
style("Skipping").yellow().bold(), style(t!("Skipping")).yellow().bold(),
repo.display() repo.display(),
t!("because it has no remotes")
); );
false false
} }

View File

@@ -1,6 +1,7 @@
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::utils::require; use crate::utils::require;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use rust_i18n::t;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
@@ -17,7 +18,7 @@ pub fn upgrade_kak_plug(ctx: &ExecutionContext) -> Result<()> {
.args(["-ui", "dummy", "-e", UPGRADE_KAK]) .args(["-ui", "dummy", "-e", UPGRADE_KAK])
.output()?; .output()?;
println!("Plugins upgraded"); println!("{}", t!("Plugins upgraded"));
Ok(()) Ok(())
} }

View File

@@ -4,11 +4,12 @@ use std::os::unix::fs::MetadataExt;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use crate::utils::{require_option, REQUIRE_SUDO}; use crate::utils::{get_require_sudo_string, require_option};
use crate::HOME_DIR; use crate::HOME_DIR;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use nix::unistd::Uid; use nix::unistd::Uid;
use rust_i18n::t;
use semver::Version; use semver::Version;
use tracing::debug; use tracing::debug;
@@ -92,7 +93,7 @@ impl NPM {
fn upgrade(&self, ctx: &ExecutionContext, use_sudo: bool) -> Result<()> { fn upgrade(&self, ctx: &ExecutionContext, use_sudo: bool) -> Result<()> {
let args = ["update", self.global_location_arg()]; let args = ["update", self.global_location_arg()];
if use_sudo { if use_sudo {
let sudo = require_option(ctx.sudo().clone(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().clone(), get_require_sudo_string())?;
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
.arg(&self.command) .arg(&self.command)
@@ -156,7 +157,7 @@ impl Yarn {
let args = ["global", "upgrade"]; let args = ["global", "upgrade"];
if use_sudo { if use_sudo {
let sudo = require_option(ctx.sudo().clone(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().clone(), get_require_sudo_string())?;
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
.arg(self.yarn.as_ref().unwrap_or(&self.command)) .arg(self.yarn.as_ref().unwrap_or(&self.command))
@@ -214,7 +215,7 @@ fn should_use_sudo_yarn(yarn: &Yarn, ctx: &ExecutionContext) -> Result<bool> {
pub fn run_npm_upgrade(ctx: &ExecutionContext) -> Result<()> { pub fn run_npm_upgrade(ctx: &ExecutionContext) -> Result<()> {
let npm = require("npm").map(|b| NPM::new(b, NPMVariant::Npm))?; let npm = require("npm").map(|b| NPM::new(b, NPMVariant::Npm))?;
print_separator("Node Package Manager"); print_separator(t!("Node Package Manager"));
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
@@ -230,7 +231,7 @@ pub fn run_npm_upgrade(ctx: &ExecutionContext) -> Result<()> {
pub fn run_pnpm_upgrade(ctx: &ExecutionContext) -> Result<()> { pub fn run_pnpm_upgrade(ctx: &ExecutionContext) -> Result<()> {
let pnpm = require("pnpm").map(|b| NPM::new(b, NPMVariant::Pnpm))?; let pnpm = require("pnpm").map(|b| NPM::new(b, NPMVariant::Pnpm))?;
print_separator("Performant Node Package Manager"); print_separator(t!("Performant Node Package Manager"));
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
@@ -251,7 +252,7 @@ pub fn run_yarn_upgrade(ctx: &ExecutionContext) -> Result<()> {
return Ok(()); return Ok(());
} }
print_separator("Yarn Package Manager"); print_separator(t!("Yarn Package Manager"));
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
@@ -269,7 +270,7 @@ pub fn deno_upgrade(ctx: &ExecutionContext) -> Result<()> {
let deno_dir = HOME_DIR.join(".deno"); let deno_dir = HOME_DIR.join(".deno");
if !deno.canonicalize()?.is_descendant_of(&deno_dir) { if !deno.canonicalize()?.is_descendant_of(&deno_dir) {
let skip_reason = SkipStep("Deno installed outside of .deno directory".to_string()); let skip_reason = SkipStep(t!("Deno installed outside of .deno directory").to_string());
return Err(skip_reason.into()); return Err(skip_reason.into());
} }
@@ -284,7 +285,7 @@ pub fn run_volta_packages_upgrade(ctx: &ExecutionContext) -> Result<()> {
print_separator("Volta"); print_separator("Volta");
if ctx.run_type().dry() { if ctx.run_type().dry() {
print_info("Updating Volta packages..."); print_info(t!("Updating Volta packages..."));
return Ok(()); return Ok(());
} }
@@ -308,7 +309,7 @@ pub fn run_volta_packages_upgrade(ctx: &ExecutionContext) -> Result<()> {
.collect(); .collect();
if installed_packages.is_empty() { if installed_packages.is_empty() {
print_info("No packages installed with Volta"); print_info(t!("No packages installed with Volta"));
return Ok(()); return Ok(());
} }

View File

@@ -4,6 +4,7 @@ use std::path::{Path, PathBuf};
use color_eyre::eyre; use color_eyre::eyre;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use rust_i18n::t;
use walkdir::WalkDir; use walkdir::WalkDir;
use crate::command::CommandExt; use crate::command::CommandExt;
@@ -310,7 +311,7 @@ impl ArchPackageManager for Aura {
} else { } else {
let sudo = crate::utils::require_option( let sudo = crate::utils::require_option(
ctx.sudo().as_ref(), ctx.sudo().as_ref(),
"Aura(<0.4.6) requires sudo installed to work with AUR packages".into(), t!("Aura(<0.4.6) requires sudo installed to work with AUR packages").to_string(),
)?; )?;
let mut cmd = ctx.run_type().execute(sudo); let mut cmd = ctx.run_type().execute(sudo);
@@ -383,7 +384,7 @@ pub fn show_pacnew() {
.peekable(); .peekable();
if iter.peek().is_some() { if iter.peek().is_some() {
println!("\nPacman backup configuration files found:"); println!("\n{}", t!("Pacman backup configuration files found:"));
for entry in iter { for entry in iter {
println!("{}", entry.path().display()); println!("{}", entry.path().display());

View File

@@ -1,14 +1,14 @@
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::utils::{require_option, REQUIRE_SUDO}; use crate::utils::{get_require_sudo_string, require_option};
use crate::Step; use crate::Step;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use std::process::Command; use std::process::Command;
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> { pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
print_separator("DragonFly BSD Packages"); print_separator(t!("DragonFly BSD Packages"));
let mut cmd = ctx.run_type().execute(sudo); let mut cmd = ctx.run_type().execute(sudo);
cmd.args(["/usr/local/sbin/pkg", "upgrade"]); cmd.args(["/usr/local/sbin/pkg", "upgrade"]);
if ctx.config().yes(Step::System) { if ctx.config().yes(Step::System) {
@@ -18,9 +18,9 @@ pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn audit_packages(ctx: &ExecutionContext) -> Result<()> { pub fn audit_packages(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
print_separator("DragonFly BSD Audit"); print_separator(t!("DragonFly BSD Audit"));
#[allow(clippy::disallowed_methods)] #[allow(clippy::disallowed_methods)]
if !Command::new(sudo) if !Command::new(sudo)
@@ -28,7 +28,9 @@ pub fn audit_packages(ctx: &ExecutionContext) -> Result<()> {
.status()? .status()?
.success() .success()
{ {
println!("The package audit was successful, but vulnerable packages still remain on the system"); println!(t!(
"The package audit was successful, but vulnerable packages still remain on the system"
));
} }
Ok(()) Ok(())
} }

View File

@@ -1,14 +1,15 @@
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::utils::{require_option, REQUIRE_SUDO}; use crate::utils::{get_require_sudo_string, require_option};
use crate::Step; use crate::Step;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use rust_i18n::t;
use std::process::Command; use std::process::Command;
pub fn upgrade_freebsd(ctx: &ExecutionContext) -> Result<()> { pub fn upgrade_freebsd(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
print_separator("FreeBSD Update"); print_separator(t!("FreeBSD Update"));
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
.args(["/usr/sbin/freebsd-update", "fetch", "install"]) .args(["/usr/sbin/freebsd-update", "fetch", "install"])
@@ -16,8 +17,8 @@ pub fn upgrade_freebsd(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> { pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
print_separator("FreeBSD Packages"); print_separator(t!("FreeBSD Packages"));
let mut command = ctx.run_type().execute(sudo); let mut command = ctx.run_type().execute(sudo);
@@ -29,9 +30,9 @@ pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn audit_packages(ctx: &ExecutionContext) -> Result<()> { pub fn audit_packages(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
print_separator("FreeBSD Audit"); print_separator(t!("FreeBSD Audit"));
Command::new(sudo) Command::new(sudo)
.args(["/usr/sbin/pkg", "audit", "-Fr"]) .args(["/usr/sbin/pkg", "audit", "-Fr"])

View File

@@ -3,6 +3,7 @@ use std::process::Command;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use ini::Ini; use ini::Ini;
use rust_i18n::t;
use tracing::{debug, warn}; use tracing::{debug, warn};
use crate::command::CommandExt; use crate::command::CommandExt;
@@ -11,7 +12,7 @@ use crate::execution_context::ExecutionContext;
use crate::steps::generic::is_wsl; use crate::steps::generic::is_wsl;
use crate::steps::os::archlinux; use crate::steps::os::archlinux;
use crate::terminal::{print_separator, prompt_yesno}; use crate::terminal::{print_separator, prompt_yesno};
use crate::utils::{require, require_option, which, PathExt, REQUIRE_SUDO}; use crate::utils::{get_require_sudo_string, require, require_option, which, PathExt};
use crate::{Step, HOME_DIR}; use crate::{Step, HOME_DIR};
static OS_RELEASE_PATH: &str = "/etc/os-release"; static OS_RELEASE_PATH: &str = "/etc/os-release";
@@ -135,7 +136,7 @@ impl Distribution {
} }
pub fn upgrade(self, ctx: &ExecutionContext) -> Result<()> { pub fn upgrade(self, ctx: &ExecutionContext) -> Result<()> {
print_separator("System update"); print_separator(t!("System update"));
match self { match self {
Distribution::Alpine => upgrade_alpine_linux(ctx), Distribution::Alpine => upgrade_alpine_linux(ctx),
@@ -176,7 +177,7 @@ impl Distribution {
} }
fn update_bedrock(ctx: &ExecutionContext) -> Result<()> { fn update_bedrock(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
ctx.run_type().execute(sudo).args(["brl", "update"]); ctx.run_type().execute(sudo).args(["brl", "update"]);
@@ -201,7 +202,7 @@ fn update_bedrock(ctx: &ExecutionContext) -> Result<()> {
fn upgrade_alpine_linux(ctx: &ExecutionContext) -> Result<()> { fn upgrade_alpine_linux(ctx: &ExecutionContext) -> Result<()> {
let apk = require("apk")?; let apk = require("apk")?;
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
ctx.run_type().execute(sudo).arg(&apk).arg("update").status_checked()?; ctx.run_type().execute(sudo).arg(&apk).arg("update").status_checked()?;
ctx.run_type().execute(sudo).arg(&apk).arg("upgrade").status_checked() ctx.run_type().execute(sudo).arg(&apk).arg("upgrade").status_checked()
@@ -209,7 +210,7 @@ fn upgrade_alpine_linux(ctx: &ExecutionContext) -> Result<()> {
fn upgrade_chimera_linux(ctx: &ExecutionContext) -> Result<()> { fn upgrade_chimera_linux(ctx: &ExecutionContext) -> Result<()> {
let apk = require("apk")?; let apk = require("apk")?;
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
ctx.run_type().execute(sudo).arg(&apk).arg("update").status_checked()?; ctx.run_type().execute(sudo).arg(&apk).arg("update").status_checked()?;
ctx.run_type().execute(sudo).arg(&apk).arg("upgrade").status_checked() ctx.run_type().execute(sudo).arg(&apk).arg("upgrade").status_checked()
@@ -217,7 +218,7 @@ fn upgrade_chimera_linux(ctx: &ExecutionContext) -> Result<()> {
fn upgrade_wolfi_linux(ctx: &ExecutionContext) -> Result<()> { fn upgrade_wolfi_linux(ctx: &ExecutionContext) -> Result<()> {
let apk = require("apk")?; let apk = require("apk")?;
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
ctx.run_type().execute(sudo).arg(&apk).arg("update").status_checked()?; ctx.run_type().execute(sudo).arg(&apk).arg("update").status_checked()?;
ctx.run_type().execute(sudo).arg(&apk).arg("upgrade").status_checked() ctx.run_type().execute(sudo).arg(&apk).arg("upgrade").status_checked()
@@ -232,7 +233,7 @@ fn upgrade_redhat(ctx: &ExecutionContext) -> Result<()> {
} }
}; };
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let mut command = ctx.run_type().execute(sudo); let mut command = ctx.run_type().execute(sudo);
command command
.arg(which("dnf").unwrap_or_else(|| Path::new("yum").to_path_buf())) .arg(which("dnf").unwrap_or_else(|| Path::new("yum").to_path_buf()))
@@ -255,7 +256,7 @@ fn upgrade_redhat(ctx: &ExecutionContext) -> Result<()> {
} }
fn upgrade_nobara(ctx: &ExecutionContext) -> Result<()> { fn upgrade_nobara(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let pkg_manager = require("dnf")?; let pkg_manager = require("dnf")?;
let mut update_command = ctx.run_type().execute(sudo); let mut update_command = ctx.run_type().execute(sudo);
@@ -289,7 +290,7 @@ fn upgrade_nobara(ctx: &ExecutionContext) -> Result<()> {
} }
fn upgrade_nilrt(ctx: &ExecutionContext) -> Result<()> { fn upgrade_nilrt(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let opkg = require("opkg")?; let opkg = require("opkg")?;
ctx.run_type().execute(sudo).arg(&opkg).arg("update").status_checked()?; ctx.run_type().execute(sudo).arg(&opkg).arg("update").status_checked()?;
@@ -305,14 +306,14 @@ fn upgrade_fedora_immutable(ctx: &ExecutionContext) -> Result<()> {
} }
fn upgrade_bedrock_strata(ctx: &ExecutionContext) -> Result<()> { fn upgrade_bedrock_strata(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
ctx.run_type().execute(sudo).args(["brl", "update"]).status_checked()?; ctx.run_type().execute(sudo).args(["brl", "update"]).status_checked()?;
Ok(()) Ok(())
} }
fn upgrade_suse(ctx: &ExecutionContext) -> Result<()> { fn upgrade_suse(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
.args(["zypper", "refresh"]) .args(["zypper", "refresh"])
@@ -335,7 +336,7 @@ fn upgrade_suse(ctx: &ExecutionContext) -> Result<()> {
} }
fn upgrade_opensuse_tumbleweed(ctx: &ExecutionContext) -> Result<()> { fn upgrade_opensuse_tumbleweed(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
.args(["zypper", "refresh"]) .args(["zypper", "refresh"])
@@ -353,7 +354,7 @@ fn upgrade_opensuse_tumbleweed(ctx: &ExecutionContext) -> Result<()> {
} }
fn upgrade_suse_micro(ctx: &ExecutionContext) -> Result<()> { fn upgrade_suse_micro(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let mut cmd = ctx.run_type().execute(sudo); let mut cmd = ctx.run_type().execute(sudo);
cmd.arg("transactional-update"); cmd.arg("transactional-update");
if ctx.config().yes(Step::System) { if ctx.config().yes(Step::System) {
@@ -366,7 +367,7 @@ fn upgrade_suse_micro(ctx: &ExecutionContext) -> Result<()> {
} }
fn upgrade_openmandriva(ctx: &ExecutionContext) -> Result<()> { fn upgrade_openmandriva(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let mut command = ctx.run_type().execute(sudo); let mut command = ctx.run_type().execute(sudo);
command.arg(which("dnf").unwrap()).arg("upgrade"); command.arg(which("dnf").unwrap()).arg("upgrade");
@@ -385,7 +386,7 @@ fn upgrade_openmandriva(ctx: &ExecutionContext) -> Result<()> {
} }
fn upgrade_pclinuxos(ctx: &ExecutionContext) -> Result<()> { fn upgrade_pclinuxos(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let mut command_update = ctx.run_type().execute(sudo); let mut command_update = ctx.run_type().execute(sudo);
command_update.arg(which("apt-get").unwrap()).arg("update"); command_update.arg(which("apt-get").unwrap()).arg("update");
@@ -432,7 +433,7 @@ fn upgrade_vanilla(ctx: &ExecutionContext) -> Result<()> {
} }
fn upgrade_void(ctx: &ExecutionContext) -> Result<()> { fn upgrade_void(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let mut command = ctx.run_type().execute(sudo); let mut command = ctx.run_type().execute(sudo);
command.args(["xbps-install", "-Su", "xbps"]); command.args(["xbps-install", "-Su", "xbps"]);
if ctx.config().yes(Step::System) { if ctx.config().yes(Step::System) {
@@ -453,7 +454,7 @@ fn upgrade_void(ctx: &ExecutionContext) -> Result<()> {
fn upgrade_gentoo(ctx: &ExecutionContext) -> Result<()> { fn upgrade_gentoo(ctx: &ExecutionContext) -> Result<()> {
let run_type = ctx.run_type(); let run_type = ctx.run_type();
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
if let Some(layman) = which("layman") { if let Some(layman) = which("layman") {
run_type run_type
.execute(sudo) .execute(sudo)
@@ -462,7 +463,7 @@ fn upgrade_gentoo(ctx: &ExecutionContext) -> Result<()> {
.status_checked()?; .status_checked()?;
} }
println!("Syncing portage"); println!("{}", t!("Syncing portage"));
if let Some(ego) = which("ego") { if let Some(ego) = which("ego") {
// The Funtoo team doesn't reccomend running both ego sync and emerge --sync // The Funtoo team doesn't reccomend running both ego sync and emerge --sync
run_type.execute(sudo).arg(ego).arg("sync").status_checked()?; run_type.execute(sudo).arg(ego).arg("sync").status_checked()?;
@@ -528,7 +529,7 @@ fn upgrade_debian(ctx: &ExecutionContext) -> Result<()> {
return Ok(()); return Ok(());
} }
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
if !is_nala { if !is_nala {
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
@@ -582,7 +583,7 @@ pub fn run_deb_get(ctx: &ExecutionContext) -> Result<()> {
} }
fn upgrade_solus(ctx: &ExecutionContext) -> Result<()> { fn upgrade_solus(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let mut cmd = ctx.run_type().execute(sudo); let mut cmd = ctx.run_type().execute(sudo);
cmd.arg("eopkg"); cmd.arg("eopkg");
if ctx.config().yes(Step::System) { if ctx.config().yes(Step::System) {
@@ -691,7 +692,7 @@ pub fn run_packer_nu(ctx: &ExecutionContext) -> Result<()> {
} }
fn upgrade_clearlinux(ctx: &ExecutionContext) -> Result<()> { fn upgrade_clearlinux(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let mut cmd = ctx.run_type().execute(sudo); let mut cmd = ctx.run_type().execute(sudo);
cmd.args(["swupd", "update"]); cmd.args(["swupd", "update"]);
if ctx.config().yes(Step::System) { if ctx.config().yes(Step::System) {
@@ -703,7 +704,7 @@ fn upgrade_clearlinux(ctx: &ExecutionContext) -> Result<()> {
} }
fn upgrade_exherbo(ctx: &ExecutionContext) -> Result<()> { fn upgrade_exherbo(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
ctx.run_type().execute(sudo).args(["cave", "sync"]).status_checked()?; ctx.run_type().execute(sudo).args(["cave", "sync"]).status_checked()?;
ctx.run_type() ctx.run_type()
@@ -732,7 +733,7 @@ fn upgrade_exherbo(ctx: &ExecutionContext) -> Result<()> {
} }
fn upgrade_nixos(ctx: &ExecutionContext) -> Result<()> { fn upgrade_nixos(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let mut command = ctx.run_type().execute(sudo); let mut command = ctx.run_type().execute(sudo);
command.args(["/run/current-system/sw/bin/nixos-rebuild", "switch", "--upgrade"]); command.args(["/run/current-system/sw/bin/nixos-rebuild", "switch", "--upgrade"]);
@@ -758,7 +759,7 @@ fn upgrade_neon(ctx: &ExecutionContext) -> Result<()> {
// seems rare // seems rare
// if that comes up we need to create a Distribution::PackageKit or some such // if that comes up we need to create a Distribution::PackageKit or some such
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let pkcon = which("pkcon").unwrap(); let pkcon = which("pkcon").unwrap();
// pkcon ignores update with update and refresh provided together // pkcon ignores update with update and refresh provided together
ctx.run_type() ctx.run_type()
@@ -787,7 +788,7 @@ fn upgrade_neon(ctx: &ExecutionContext) -> Result<()> {
/// alternative /// alternative
fn should_skip_needrestart() -> Result<()> { fn should_skip_needrestart() -> Result<()> {
let distribution = Distribution::detect()?; let distribution = Distribution::detect()?;
let msg = "needrestart will be ran by the package manager"; let msg = t!("needrestart will be ran by the package manager");
if distribution.redhat_based() { if distribution.redhat_based() {
return Err(SkipStep(String::from(msg)).into()); return Err(SkipStep(String::from(msg)).into());
@@ -822,12 +823,12 @@ fn should_skip_needrestart() -> Result<()> {
} }
pub fn run_needrestart(ctx: &ExecutionContext) -> Result<()> { pub fn run_needrestart(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let needrestart = require("needrestart")?; let needrestart = require("needrestart")?;
should_skip_needrestart()?; should_skip_needrestart()?;
print_separator("Check for needed restarts"); print_separator(t!("Check for needed restarts"));
ctx.run_type().execute(sudo).arg(needrestart).status_checked()?; ctx.run_type().execute(sudo).arg(needrestart).status_checked()?;
@@ -838,10 +839,10 @@ pub fn run_fwupdmgr(ctx: &ExecutionContext) -> Result<()> {
let fwupdmgr = require("fwupdmgr")?; let fwupdmgr = require("fwupdmgr")?;
if is_wsl()? { if is_wsl()? {
return Err(SkipStep(String::from("Should not run in WSL")).into()); return Err(SkipStep(t!("Should not run in WSL").to_string()).into());
} }
print_separator("Firmware upgrades"); print_separator(t!("Firmware upgrades"));
ctx.run_type() ctx.run_type()
.execute(&fwupdmgr) .execute(&fwupdmgr)
@@ -863,7 +864,7 @@ pub fn run_fwupdmgr(ctx: &ExecutionContext) -> Result<()> {
pub fn run_flatpak(ctx: &ExecutionContext) -> Result<()> { pub fn run_flatpak(ctx: &ExecutionContext) -> Result<()> {
let flatpak = require("flatpak")?; let flatpak = require("flatpak")?;
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let cleanup = ctx.config().cleanup(); let cleanup = ctx.config().cleanup();
let yes = ctx.config().yes(Step::Flatpak); let yes = ctx.config().yes(Step::Flatpak);
let run_type = ctx.run_type(); let run_type = ctx.run_type();
@@ -883,7 +884,7 @@ pub fn run_flatpak(ctx: &ExecutionContext) -> Result<()> {
run_type.execute(&flatpak).args(&cleanup_args).status_checked()?; run_type.execute(&flatpak).args(&cleanup_args).status_checked()?;
} }
print_separator("Flatpak System Packages"); print_separator(t!("Flatpak System Packages"));
if ctx.config().flatpak_use_sudo() || std::env::var("SSH_CLIENT").is_ok() { if ctx.config().flatpak_use_sudo() || std::env::var("SSH_CLIENT").is_ok() {
let mut update_args = vec!["update", "--system"]; let mut update_args = vec!["update", "--system"];
if yes { if yes {
@@ -924,11 +925,11 @@ pub fn run_flatpak(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn run_snap(ctx: &ExecutionContext) -> Result<()> { pub fn run_snap(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let snap = require("snap")?; let snap = require("snap")?;
if !PathBuf::from("/var/snapd.socket").exists() && !PathBuf::from("/run/snapd.socket").exists() { if !PathBuf::from("/var/snapd.socket").exists() && !PathBuf::from("/run/snapd.socket").exists() {
return Err(SkipStep(String::from("Snapd socket does not exist")).into()); return Err(SkipStep(t!("Snapd socket does not exist").to_string()).into());
} }
print_separator("snap"); print_separator("snap");
@@ -936,7 +937,7 @@ pub fn run_snap(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn run_pihole_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_pihole_update(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let pihole = require("pihole")?; let pihole = require("pihole")?;
Path::new("/opt/pihole/update.sh").require()?; Path::new("/opt/pihole/update.sh").require()?;
@@ -970,7 +971,7 @@ pub fn run_distrobox_update(ctx: &ExecutionContext) -> Result<()> {
) { ) {
(r, Some(c)) => { (r, Some(c)) => {
if c.is_empty() { if c.is_empty() {
return Err(SkipStep("You need to specify at least one container".to_string()).into()); return Err(SkipStep(t!("You need to specify at least one container").to_string()).into());
} }
r.args(c) r.args(c)
} }
@@ -985,7 +986,7 @@ pub fn run_distrobox_update(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn run_dkp_pacman_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_dkp_pacman_update(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let dkp_pacman = require("dkp-pacman")?; let dkp_pacman = require("dkp-pacman")?;
print_separator("Devkitpro pacman"); print_separator("Devkitpro pacman");
@@ -1008,20 +1009,20 @@ pub fn run_dkp_pacman_update(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn run_config_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_config_update(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
if ctx.config().yes(Step::ConfigUpdate) { if ctx.config().yes(Step::ConfigUpdate) {
return Err(SkipStep("Skipped in --yes".to_string()).into()); return Err(SkipStep(t!("Skipped in --yes").to_string()).into());
} }
if let Ok(etc_update) = require("etc-update") { if let Ok(etc_update) = require("etc-update") {
print_separator("Configuration update"); print_separator(t!("Configuration update"));
ctx.run_type().execute(sudo).arg(etc_update).status_checked()?; ctx.run_type().execute(sudo).arg(etc_update).status_checked()?;
} else if let Ok(pacdiff) = require("pacdiff") { } else if let Ok(pacdiff) = require("pacdiff") {
if std::env::var("DIFFPROG").is_err() { if std::env::var("DIFFPROG").is_err() {
require("vim")?; require("vim")?;
} }
print_separator("Configuration update"); print_separator(t!("Configuration update"));
ctx.execute_elevated(&pacdiff, false)?.status_checked()?; ctx.execute_elevated(&pacdiff, false)?.status_checked()?;
} }
@@ -1045,7 +1046,7 @@ pub fn run_lure_update(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn run_waydroid(ctx: &ExecutionContext) -> Result<()> { pub fn run_waydroid(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let waydroid = require("waydroid")?; let waydroid = require("waydroid")?;
let status = ctx.run_type().execute(&waydroid).arg("status").output_checked_utf8()?; let status = ctx.run_type().execute(&waydroid).arg("status").output_checked_utf8()?;
// example output of `waydroid status`: // example output of `waydroid status`:
@@ -1069,17 +1070,20 @@ pub fn run_waydroid(ctx: &ExecutionContext) -> Result<()> {
.stdout .stdout
.lines() .lines()
.find(|line| line.contains("Session:")) .find(|line| line.contains("Session:"))
.expect("the output of `waydroid status` should contain `Session:`"); .unwrap_or_else(|| panic!("the output of `waydroid status` should contain `Session:`"));
let is_container_running = session.contains("RUNNING"); let is_container_running = session.contains("RUNNING");
let assume_yes = ctx.config().yes(Step::Waydroid); let assume_yes = ctx.config().yes(Step::Waydroid);
print_separator("Waydroid"); print_separator("Waydroid");
if is_container_running && !assume_yes { if is_container_running && !assume_yes {
let update_allowed = let update_allowed = prompt_yesno(&t!(
prompt_yesno("Going to execute `waydroid upgrade`, which would STOP the running container, is this ok?")?; "Going to execute `waydroid upgrade`, which would STOP the running container, is this ok?"
))?;
if !update_allowed { if !update_allowed {
return Err(SkipStep("Skip the Waydroid step because the user don't want to proceed".to_string()).into()); return Err(
SkipStep(t!("Skip the Waydroid step because the user don't want to proceed").to_string()).into(),
);
} }
} }
ctx.run_type() ctx.run_type()
@@ -1090,7 +1094,7 @@ pub fn run_waydroid(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn run_auto_cpufreq(ctx: &ExecutionContext) -> Result<()> { pub fn run_auto_cpufreq(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let auto_cpu_freq = require("auto-cpufreq")?; let auto_cpu_freq = require("auto-cpufreq")?;
print_separator("auto-cpufreq"); print_separator("auto-cpufreq");

View File

@@ -1,9 +1,10 @@
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::terminal::{print_separator, prompt_yesno}; use crate::terminal::{print_separator, prompt_yesno};
use crate::utils::{require_option, REQUIRE_SUDO}; use crate::utils::{get_require_sudo_string, require_option};
use crate::{utils::require, Step}; use crate::{utils::require, Step};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use rust_i18n::t;
use std::collections::HashSet; use std::collections::HashSet;
use std::fs; use std::fs;
use std::process::Command; use std::process::Command;
@@ -11,7 +12,7 @@ use tracing::debug;
pub fn run_macports(ctx: &ExecutionContext) -> Result<()> { pub fn run_macports(ctx: &ExecutionContext) -> Result<()> {
require("port")?; require("port")?;
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
print_separator("MacPorts"); print_separator("MacPorts");
ctx.run_type() ctx.run_type()
@@ -34,25 +35,25 @@ pub fn run_macports(ctx: &ExecutionContext) -> Result<()> {
pub fn run_mas(ctx: &ExecutionContext) -> Result<()> { pub fn run_mas(ctx: &ExecutionContext) -> Result<()> {
let mas = require("mas")?; let mas = require("mas")?;
print_separator("macOS App Store"); print_separator(t!("macOS App Store"));
ctx.run_type().execute(mas).arg("upgrade").status_checked() ctx.run_type().execute(mas).arg("upgrade").status_checked()
} }
pub fn upgrade_macos(ctx: &ExecutionContext) -> Result<()> { pub fn upgrade_macos(ctx: &ExecutionContext) -> Result<()> {
print_separator("macOS system update"); print_separator(t!("macOS system update"));
let should_ask = !(ctx.config().yes(Step::System) || ctx.config().dry_run()); let should_ask = !(ctx.config().yes(Step::System) || ctx.config().dry_run());
if should_ask { if should_ask {
println!("Finding available software"); println!("{}", t!("Finding available software"));
if system_update_available()? { if system_update_available()? {
let answer = prompt_yesno("A system update is available. Do you wish to install it?")?; let answer = prompt_yesno(t!("A system update is available. Do you wish to install it?").as_ref())?;
if !answer { if !answer {
return Ok(()); return Ok(());
} }
println!(); println!();
} else { } else {
println!("No new software available."); println!("{}", t!("No new software available."));
return Ok(()); return Ok(());
} }
} }
@@ -115,7 +116,7 @@ pub fn update_xcodes(ctx: &ExecutionContext) -> Result<()> {
.collect(); .collect();
if releases_installed.is_empty() { if releases_installed.is_empty() {
println!("No Xcode releases installed."); println!("{}", t!("No Xcode releases installed."));
return Ok(()); return Ok(());
} }
@@ -194,7 +195,8 @@ pub fn update_xcodes(ctx: &ExecutionContext) -> Result<()> {
releases_regular_new_installed, releases_regular_new_installed,
] { ] {
if should_ask && releases_new_installed.len() == 2 { if should_ask && releases_new_installed.len() == 2 {
let answer_uninstall = prompt_yesno("Would you like to move the former Xcode release to the trash?")?; let answer_uninstall =
prompt_yesno(t!("Would you like to move the former Xcode release to the trash?").as_ref())?;
if answer_uninstall { if answer_uninstall {
let _ = ctx let _ = ctx
.run_type() .run_type()
@@ -221,11 +223,12 @@ pub fn process_xcodes_releases(releases_filtered: Vec<String>, should_ask: bool,
&& !releases_filtered.is_empty() && !releases_filtered.is_empty()
{ {
println!( println!(
"New Xcode release detected: {}", "{} {}",
t!("New Xcode release detected:"),
releases_filtered.last().cloned().unwrap_or_default() releases_filtered.last().cloned().unwrap_or_default()
); );
if should_ask { if should_ask {
let answer_install = prompt_yesno("Would you like to install it?")?; let answer_install = prompt_yesno(t!("Would you like to install it?").as_ref())?;
if answer_install { if answer_install {
let _ = ctx let _ = ctx
.run_type() .run_type()

View File

@@ -1,12 +1,12 @@
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::utils::{require_option, REQUIRE_SUDO}; use crate::utils::{get_require_sudo_string, require_option};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
pub fn upgrade_openbsd(ctx: &ExecutionContext) -> Result<()> { pub fn upgrade_openbsd(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
print_separator("OpenBSD Update"); print_separator(t!("OpenBSD Update"));
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
.args(["/usr/sbin/sysupgrade", "-n"]) .args(["/usr/sbin/sysupgrade", "-n"])
@@ -14,8 +14,8 @@ pub fn upgrade_openbsd(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> { pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
print_separator("OpenBSD Packages"); print_separator(t!("OpenBSD Packages"));
if ctx.config().cleanup() { if ctx.config().cleanup() {
ctx.run_type() ctx.run_type()

View File

@@ -15,6 +15,7 @@ use home;
use ini::Ini; use ini::Ini;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use nix::unistd::Uid; use nix::unistd::Uid;
use rust_i18n::t;
use semver::Version; use semver::Version;
use tracing::debug; use tracing::debug;
@@ -27,7 +28,7 @@ use crate::executor::Executor;
#[cfg(any(target_os = "linux", target_os = "macos"))] #[cfg(any(target_os = "linux", target_os = "macos"))]
use crate::executor::RunType; use crate::executor::RunType;
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::utils::{require, require_option, PathExt, REQUIRE_SUDO}; use crate::utils::{get_require_sudo_string, require, require_option, PathExt};
#[cfg(any(target_os = "linux", target_os = "macos"))] #[cfg(any(target_os = "linux", target_os = "macos"))]
const INTEL_BREW: &str = "/usr/local/bin/brew"; const INTEL_BREW: &str = "/usr/local/bin/brew";
@@ -101,19 +102,19 @@ pub fn run_fisher(ctx: &ExecutionContext) -> Result<()> {
.args(["-c", "type -t fisher"]) .args(["-c", "type -t fisher"])
.output_checked_utf8() .output_checked_utf8()
.map(|_| ()) .map(|_| ())
.map_err(|_| SkipStep("`fisher` is not defined in `fish`".to_owned()))?; .map_err(|_| SkipStep(t!("`fisher` is not defined in `fish`").to_string()))?;
Command::new(&fish) Command::new(&fish)
.args(["-c", "echo \"$__fish_config_dir/fish_plugins\""]) .args(["-c", "echo \"$__fish_config_dir/fish_plugins\""])
.output_checked_utf8() .output_checked_utf8()
.and_then(|output| Path::new(&output.stdout.trim()).require().map(|_| ())) .and_then(|output| Path::new(&output.stdout.trim()).require().map(|_| ()))
.map_err(|err| SkipStep(format!("`fish_plugins` path doesn't exist: {err}")))?; .map_err(|err| SkipStep(t!("`fish_plugins` path doesn't exist: {err}", err = err).to_string()))?;
Command::new(&fish) Command::new(&fish)
.args(["-c", "fish_update_completions"]) .args(["-c", "fish_update_completions"])
.output_checked_utf8() .output_checked_utf8()
.map(|_| ()) .map(|_| ())
.map_err(|_| SkipStep("`fish_update_completions` is not available".to_owned()))?; .map_err(|_| SkipStep(t!("`fish_update_completions` is not available").to_string()))?;
print_separator("Fisher"); print_separator("Fisher");
@@ -180,7 +181,7 @@ pub fn run_oh_my_fish(ctx: &ExecutionContext) -> Result<()> {
pub fn run_pkgin(ctx: &ExecutionContext) -> Result<()> { pub fn run_pkgin(ctx: &ExecutionContext) -> Result<()> {
let pkgin = require("pkgin")?; let pkgin = require("pkgin")?;
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
print_separator("Pkgin"); print_separator("Pkgin");
@@ -235,7 +236,7 @@ pub fn upgrade_gnome_extensions(ctx: &ExecutionContext) -> Result<()> {
let gdbus = require("gdbus")?; let gdbus = require("gdbus")?;
require_option( require_option(
var("XDG_CURRENT_DESKTOP").ok().filter(|p| p.contains("GNOME")), var("XDG_CURRENT_DESKTOP").ok().filter(|p| p.contains("GNOME")),
"Desktop doest not appear to be gnome".to_string(), t!("Desktop doest not appear to be gnome").to_string(),
)?; )?;
let output = Command::new("gdbus") let output = Command::new("gdbus")
.args([ .args([
@@ -252,10 +253,10 @@ pub fn upgrade_gnome_extensions(ctx: &ExecutionContext) -> Result<()> {
debug!("Checking for gnome extensions: {}", output); debug!("Checking for gnome extensions: {}", output);
if !output.stdout.contains("org.gnome.Shell.Extensions") { if !output.stdout.contains("org.gnome.Shell.Extensions") {
return Err(SkipStep(String::from("Gnome shell extensions are unregistered in DBus")).into()); return Err(SkipStep(t!("Gnome shell extensions are unregistered in DBus").to_string()).into());
} }
print_separator("Gnome Shell extensions"); print_separator(t!("Gnome Shell extensions"));
ctx.run_type() ctx.run_type()
.execute(gdbus) .execute(gdbus)
@@ -297,7 +298,7 @@ pub fn run_brew_formula(ctx: &ExecutionContext, variant: BrewVariant) -> Result<
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
if variant.is_path() && !BrewVariant::is_macos_custom(binary_name) { if variant.is_path() && !BrewVariant::is_macos_custom(binary_name) {
return Err(SkipStep("Not a custom brew for macOS".to_string()).into()); return Err(SkipStep(t!("Not a custom brew for macOS").to_string()).into());
} }
} }
@@ -310,8 +311,11 @@ pub fn run_brew_formula(ctx: &ExecutionContext, variant: BrewVariant) -> Result<
let user = nix::unistd::User::from_uid(uid) let user = nix::unistd::User::from_uid(uid)
.expect("failed to call getpwuid()") .expect("failed to call getpwuid()")
.expect("this user should exist"); .expect("this user should exist");
print_separator(format!("{} (sudo as user '{}')", variant.step_title(), user.name));
let sudo = crate::utils::require_option(ctx.sudo().as_ref(), "sudo is needed to run the update".into())?; let sudo_as_user = t!("sudo as user '{user}'", user = user.name);
print_separator(format!("{} ({})", variant.step_title(), sudo_as_user));
let sudo = crate::utils::require_option(ctx.sudo().as_ref(), crate::utils::get_require_sudo_string())?;
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
.current_dir("/tmp") // brew needs a writable current directory .current_dir("/tmp") // brew needs a writable current directory
@@ -354,7 +358,7 @@ pub fn run_brew_formula(ctx: &ExecutionContext, variant: BrewVariant) -> Result<
pub fn run_brew_cask(ctx: &ExecutionContext, variant: BrewVariant) -> Result<()> { pub fn run_brew_cask(ctx: &ExecutionContext, variant: BrewVariant) -> Result<()> {
let binary_name = require(variant.binary_name())?; let binary_name = require(variant.binary_name())?;
if variant.is_path() && !BrewVariant::is_macos_custom(binary_name) { if variant.is_path() && !BrewVariant::is_macos_custom(binary_name) {
return Err(SkipStep("Not a custom brew for macOS".to_string()).into()); return Err(SkipStep(t!("Not a custom brew for macOS").to_string()).into());
} }
print_separator(format!("{} - Cask", variant.step_title())); print_separator(format!("{} - Cask", variant.step_title()));
let run_type = ctx.run_type(); let run_type = ctx.run_type();
@@ -409,7 +413,7 @@ pub fn run_guix(ctx: &ExecutionContext) -> Result<()> {
if should_upgrade { if should_upgrade {
return run_type.execute(&guix).args(["package", "-u"]).status_checked(); return run_type.execute(&guix).args(["package", "-u"]).status_checked();
} }
Err(SkipStep(String::from("Guix Pull Failed, Skipping")).into()) Err(SkipStep(t!("Guix Pull Failed, Skipping").to_string()).into())
} }
pub fn run_nix(ctx: &ExecutionContext) -> Result<()> { pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
@@ -429,41 +433,38 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
if require("darwin-rebuild").is_ok() { if require("darwin-rebuild").is_ok() {
return Err(SkipStep(String::from( return Err(
"Nix-darwin on macOS must be upgraded via darwin-rebuild switch", SkipStep(t!("Nix-darwin on macOS must be upgraded via darwin-rebuild switch").to_string()).into(),
)) );
.into());
} }
} }
let run_type = ctx.run_type(); let run_type = ctx.run_type();
run_type.execute(nix_channel).arg("--update").status_checked()?; run_type.execute(nix_channel).arg("--update").status_checked()?;
let version: Result<Version> = match Command::new(&nix) let mut get_version_cmd = ctx.run_type().execute(&nix);
.arg("--version") get_version_cmd.arg("--version");
.output_checked_utf8()? let get_version_cmd_output = get_version_cmd.output_checked_utf8()?;
let get_version_cmd_first_line_stdout = get_version_cmd_output
.stdout .stdout
.lines() .lines()
.next() .next()
{ .expect("nix --version gives an empty output");
Some(item) => { let splitted: Vec<&str> = get_version_cmd_first_line_stdout.split_whitespace().collect();
let parts: Vec<&str> = item.split_whitespace().collect(); let version = if splitted.len() >= 3 {
if parts.len() >= 3 { Version::parse(splitted[2]).expect("invalid version")
Version::parse(parts[2]).map_err(|err| err.into()) } else {
} else { panic!("nix --version output format changed, file an issue to Topgrade!")
Err(SkipStep(String::from("Unexpected version format")).into())
}
}
_ => return Err(SkipStep(String::from("Cannot find nix version")).into()),
}; };
debug!("Nix version: {:?}", version); debug!("Nix version: {:?}", version);
let mut packages: Vec<&str> = vec!["--all", "--impure"]; // Nix since 2.21.0 uses `--all --impure` rather than `.*` to upgrade all packages
let packages = if version >= Version::new(2, 21, 0) {
if !matches!(version, Ok(version) if version >= Version::new(2, 21, 0)) { vec!["--all", "--impure"]
packages = vec![".*"]; } else {
} vec![".*"]
};
if Path::new(&manifest_json_path).exists() { if Path::new(&manifest_json_path).exists() {
run_type run_type
@@ -500,20 +501,16 @@ pub fn run_nix_self_upgrade(ctx: &ExecutionContext) -> Result<()> {
} }
if !should_self_upgrade { if !should_self_upgrade {
return Err(SkipStep(String::from( return Err(SkipStep(t!("`nix upgrade-nix` can only be used on macOS or non-NixOS Linux").to_string()).into());
"`nix upgrade-nix` can only be used on macOS or non-NixOS Linux",
))
.into());
} }
if nix_profile_dir(&nix)?.is_none() { if nix_profile_dir(&nix)?.is_none() {
return Err(SkipStep(String::from( return Err(
"`nix upgrade-nix` cannot be run when Nix is installed in a profile", SkipStep(t!("`nix upgrade-nix` cannot be run when Nix is installed in a profile").to_string()).into(),
)) );
.into());
} }
print_separator("Nix (self-upgrade)"); print_separator(t!("Nix (self-upgrade)"));
let multi_user = fs::metadata(&nix)?.uid() == 0; let multi_user = fs::metadata(&nix)?.uid() == 0;
debug!("Multi user nix: {}", multi_user); debug!("Multi user nix: {}", multi_user);
@@ -578,7 +575,6 @@ fn nix_profile_dir(nix: &Path) -> Result<Option<PathBuf>> {
} }
debug!("Found Nix profile {profile_dir:?}"); debug!("Found Nix profile {profile_dir:?}");
let user_env = profile_dir let user_env = profile_dir
.canonicalize() .canonicalize()
.wrap_err_with(|| format!("Failed to canonicalize {profile_dir:?}"))?; .wrap_err_with(|| format!("Failed to canonicalize {profile_dir:?}"))?;
@@ -675,15 +671,15 @@ pub fn run_pyenv(ctx: &ExecutionContext) -> Result<()> {
.unwrap_or_else(|_| HOME_DIR.join(".pyenv")); .unwrap_or_else(|_| HOME_DIR.join(".pyenv"));
if !pyenv_dir.exists() { if !pyenv_dir.exists() {
return Err(SkipStep("Pyenv is installed, but $PYENV_ROOT is not set correctly".to_string()).into()); return Err(SkipStep(t!("Pyenv is installed, but $PYENV_ROOT is not set correctly").to_string()).into());
} }
if !pyenv_dir.join(".git").exists() { if !pyenv_dir.join(".git").exists() {
return Err(SkipStep("pyenv is not a git repository".to_string()).into()); return Err(SkipStep(t!("pyenv is not a git repository").to_string()).into());
} }
if !pyenv_dir.join("plugins").join("pyenv-update").exists() { if !pyenv_dir.join("plugins").join("pyenv-update").exists() {
return Err(SkipStep("pyenv-update plugin is not installed".to_string()).into()); return Err(SkipStep(t!("pyenv-update plugin is not installed").to_string()).into());
} }
ctx.run_type().execute(pyenv).arg("update").status_checked() ctx.run_type().execute(pyenv).arg("update").status_checked()
@@ -755,7 +751,7 @@ pub fn run_sdkman(ctx: &ExecutionContext) -> Result<()> {
pub fn run_bun_packages(ctx: &ExecutionContext) -> Result<()> { pub fn run_bun_packages(ctx: &ExecutionContext) -> Result<()> {
let bun = require("bun")?; let bun = require("bun")?;
print_separator("Bun Packages"); print_separator(t!("Bun Packages"));
let mut package_json: PathBuf = var("BUN_INSTALL") let mut package_json: PathBuf = var("BUN_INSTALL")
.map(PathBuf::from) .map(PathBuf::from)
@@ -763,7 +759,7 @@ pub fn run_bun_packages(ctx: &ExecutionContext) -> Result<()> {
package_json.push("install/global/package.json"); package_json.push("install/global/package.json");
if !package_json.exists() { if !package_json.exists() {
println!("No global packages installed"); println!("{}", t!("No global packages installed"));
return Ok(()); return Ok(());
} }
@@ -788,7 +784,7 @@ pub fn run_maza(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn reboot() -> Result<()> { pub fn reboot() -> Result<()> {
print!("Rebooting..."); print!("{}", t!("Rebooting..."));
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(target_os = "linux")] { if #[cfg(target_os = "linux")] {

View File

@@ -11,6 +11,7 @@ use crate::terminal::{print_separator, print_warning};
use crate::utils::{require, which}; use crate::utils::{require, which};
use crate::{error::SkipStep, steps::git::RepoStep}; use crate::{error::SkipStep, steps::git::RepoStep};
use crate::{powershell, Step}; use crate::{powershell, Step};
use rust_i18n::t;
pub fn run_chocolatey(ctx: &ExecutionContext) -> Result<()> { pub fn run_chocolatey(ctx: &ExecutionContext) -> Result<()> {
let choco = require("choco")?; let choco = require("choco")?;
@@ -68,12 +69,12 @@ pub fn run_scoop(ctx: &ExecutionContext) -> Result<()> {
pub fn update_wsl(ctx: &ExecutionContext) -> Result<()> { pub fn update_wsl(ctx: &ExecutionContext) -> Result<()> {
if !is_wsl_installed()? { if !is_wsl_installed()? {
return Err(SkipStep("WSL not installed".to_string()).into()); return Err(SkipStep(t!("WSL not installed").to_string()).into());
} }
let wsl = require("wsl")?; let wsl = require("wsl")?;
print_separator("Update WSL"); print_separator(t!("Update WSL"));
let mut wsl_command = ctx.run_type().execute(wsl); let mut wsl_command = ctx.run_type().execute(wsl);
wsl_command.args(["--update"]); wsl_command.args(["--update"]);
@@ -126,7 +127,7 @@ fn upgrade_wsl_distribution(wsl: &Path, dist: &str, ctx: &ExecutionContext) -> R
let topgrade = Command::new(wsl) let topgrade = Command::new(wsl)
.args(["-d", dist, "bash", "-lc", "which topgrade"]) .args(["-d", dist, "bash", "-lc", "which topgrade"])
.output_checked_utf8() .output_checked_utf8()
.map_err(|_| SkipStep(String::from("Could not find Topgrade installed in WSL")))? .map_err(|_| SkipStep(t!("Could not find Topgrade installed in WSL").to_string()))?
.stdout // The normal output from `which topgrade` appends a newline, so we trim it here. .stdout // The normal output from `which topgrade` appends a newline, so we trim it here.
.trim_end() .trim_end()
.to_owned(); .to_owned();
@@ -175,7 +176,7 @@ fn upgrade_wsl_distribution(wsl: &Path, dist: &str, ctx: &ExecutionContext) -> R
pub fn run_wsl_topgrade(ctx: &ExecutionContext) -> Result<()> { pub fn run_wsl_topgrade(ctx: &ExecutionContext) -> Result<()> {
if !is_wsl_installed()? { if !is_wsl_installed()? {
return Err(SkipStep("WSL not installed".to_string()).into()); return Err(SkipStep(t!("WSL not installed").to_string()).into());
} }
let wsl = require("wsl")?; let wsl = require("wsl")?;
@@ -198,25 +199,25 @@ pub fn run_wsl_topgrade(ctx: &ExecutionContext) -> Result<()> {
if ran { if ran {
Ok(()) Ok(())
} else { } else {
Err(SkipStep(String::from("Could not find Topgrade in any WSL disribution")).into()) Err(SkipStep(t!("Could not find Topgrade in any WSL disribution").to_string()).into())
} }
} }
pub fn windows_update(ctx: &ExecutionContext) -> Result<()> { pub fn windows_update(ctx: &ExecutionContext) -> Result<()> {
let powershell = powershell::Powershell::windows_powershell(); let powershell = powershell::Powershell::windows_powershell();
print_separator("Windows Update"); print_separator(t!("Windows Update"));
if powershell.supports_windows_update() { if powershell.supports_windows_update() {
println!("The installer will request to run as administrator, expect a prompt."); println!("The installer will request to run as administrator, expect a prompt.");
powershell.windows_update(ctx) powershell.windows_update(ctx)
} else { } else {
print_warning( print_warning(t!(
"Consider installing PSWindowsUpdate Module as the use of Windows Update via USOClient is not supported.", "Consider installing PSWindowsUpdate as the use of Windows Update via USOClient is not supported."
); ));
Err(SkipStep("USOClient not supported.".to_string()).into()) Err(SkipStep(t!("USOClient not supported.").to_string()).into())
} }
} }

View File

@@ -4,6 +4,7 @@ use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use rust_i18n::t;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
@@ -62,9 +63,9 @@ impl Powershell {
} }
pub fn update_modules(&self, ctx: &ExecutionContext) -> Result<()> { pub fn update_modules(&self, ctx: &ExecutionContext) -> Result<()> {
let powershell = require_option(self.path.as_ref(), String::from("Powershell is not installed"))?; let powershell = require_option(self.path.as_ref(), t!("Powershell is not installed").to_string())?;
print_separator("Powershell Modules Update"); print_separator(t!("Powershell Modules Update"));
let mut cmd = vec!["Update-Module"]; let mut cmd = vec!["Update-Module"];
@@ -76,7 +77,7 @@ impl Powershell {
cmd.push("-Force") cmd.push("-Force")
} }
println!("Updating modules..."); println!("{}", t!("Updating modules..."));
ctx.run_type() ctx.run_type()
.execute(powershell) .execute(powershell)
// This probably doesn't need `shell_words::join`. // This probably doesn't need `shell_words::join`.
@@ -94,7 +95,8 @@ impl Powershell {
#[cfg(windows)] #[cfg(windows)]
pub fn windows_update(&self, ctx: &ExecutionContext) -> Result<()> { pub fn windows_update(&self, ctx: &ExecutionContext) -> Result<()> {
let powershell = require_option(self.path.as_ref(), String::from("Powershell is not installed"))?; let powershell = require_option(self.path.as_ref(), t!("Powershell is not installed").to_string())?;
debug_assert!(self.supports_windows_update()); debug_assert!(self.supports_windows_update());
let accept_all = if ctx.config().accept_all_windows_updates() { let accept_all = if ctx.config().accept_all_windows_updates() {

View File

@@ -1,54 +1,55 @@
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use rust_i18n::t;
use crate::{
command::CommandExt, error::SkipStep, execution_context::ExecutionContext, terminal::print_separator, utils, use crate::{
}; command::CommandExt, error::SkipStep, execution_context::ExecutionContext, terminal::print_separator, utils,
};
fn prepare_async_ssh_command(args: &mut Vec<&str>) {
args.insert(0, "ssh"); fn prepare_async_ssh_command(args: &mut Vec<&str>) {
args.push("--keep"); args.insert(0, "ssh");
} args.push("--keep");
}
pub fn ssh_step(ctx: &ExecutionContext, hostname: &str) -> Result<()> {
let ssh = utils::require("ssh")?; pub fn ssh_step(ctx: &ExecutionContext, hostname: &str) -> Result<()> {
let ssh = utils::require("ssh")?;
let topgrade = ctx.config().remote_topgrade_path();
let mut args = vec!["-t", hostname]; let topgrade = ctx.config().remote_topgrade_path();
let mut args = vec!["-t", hostname];
if let Some(ssh_arguments) = ctx.config().ssh_arguments() {
args.extend(ssh_arguments.split_whitespace()); if let Some(ssh_arguments) = ctx.config().ssh_arguments() {
} args.extend(ssh_arguments.split_whitespace());
}
let env = format!("TOPGRADE_PREFIX={hostname}");
args.extend(["env", &env, "$SHELL", "-lc", topgrade]); let env = format!("TOPGRADE_PREFIX={hostname}");
args.extend(["env", &env, "$SHELL", "-lc", topgrade]);
if ctx.config().run_in_tmux() && !ctx.run_type().dry() {
#[cfg(unix)] if ctx.config().run_in_tmux() && !ctx.run_type().dry() {
{ #[cfg(unix)]
prepare_async_ssh_command(&mut args); {
crate::tmux::run_command(ctx, hostname, &shell_words::join(args))?; prepare_async_ssh_command(&mut args);
Err(SkipStep(String::from("Remote Topgrade launched in Tmux")).into()) crate::tmux::run_command(ctx, hostname, &shell_words::join(args))?;
} Err(SkipStep(String::from(t!("Remote Topgrade launched in Tmux"))).into())
}
#[cfg(not(unix))]
unreachable!("Tmux execution is only implemented in Unix"); #[cfg(not(unix))]
} else if ctx.config().open_remotes_in_new_terminal() && !ctx.run_type().dry() && cfg!(windows) { unreachable!("Tmux execution is only implemented in Unix");
prepare_async_ssh_command(&mut args); } else if ctx.config().open_remotes_in_new_terminal() && !ctx.run_type().dry() && cfg!(windows) {
ctx.run_type().execute("wt").args(&args).spawn()?; prepare_async_ssh_command(&mut args);
Err(SkipStep(String::from("Remote Topgrade launched in an external terminal")).into()) ctx.run_type().execute("wt").args(&args).spawn()?;
} else { Err(SkipStep(String::from(t!("Remote Topgrade launched in an external terminal"))).into())
let mut args = vec!["-t", hostname]; } else {
let mut args = vec!["-t", hostname];
if let Some(ssh_arguments) = ctx.config().ssh_arguments() {
args.extend(ssh_arguments.split_whitespace()); if let Some(ssh_arguments) = ctx.config().ssh_arguments() {
} args.extend(ssh_arguments.split_whitespace());
}
let env = format!("TOPGRADE_PREFIX={hostname}");
args.extend(["env", &env, "$SHELL", "-lc", topgrade]); let env = format!("TOPGRADE_PREFIX={hostname}");
args.extend(["env", &env, "$SHELL", "-lc", topgrade]);
print_separator(format!("Remote ({hostname})"));
println!("Connecting to {hostname}..."); print_separator(format!("Remote ({hostname})"));
println!("{}", t!("Connecting to {hostname}...", hostname = hostname));
ctx.run_type().execute(ssh).args(&args).status_checked()
} ctx.run_type().execute(ssh).args(&args).status_checked()
} }
}

View File

@@ -4,6 +4,7 @@ use std::{fmt::Display, rc::Rc, str::FromStr};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use regex::Regex; use regex::Regex;
use rust_i18n::t;
use strum::EnumString; use strum::EnumString;
use tracing::{debug, error}; use tracing::{debug, error};
@@ -151,14 +152,14 @@ impl<'a> Drop for TemporaryPowerOn<'a> {
pub fn collect_boxes(ctx: &ExecutionContext) -> Result<Vec<VagrantBox>> { pub fn collect_boxes(ctx: &ExecutionContext) -> Result<Vec<VagrantBox>> {
let directories = utils::require_option( let directories = utils::require_option(
ctx.config().vagrant_directories(), ctx.config().vagrant_directories(),
String::from("No Vagrant directories were specified in the configuration file"), String::from(t!("No Vagrant directories were specified in the configuration file")),
)?; )?;
let vagrant = Vagrant { let vagrant = Vagrant {
path: utils::require("vagrant")?, path: utils::require("vagrant")?,
}; };
print_separator("Vagrant"); print_separator("Vagrant");
println!("Collecting Vagrant boxes"); println!("{}", t!("Collecting Vagrant boxes"));
let mut result = Vec::new(); let mut result = Vec::new();
@@ -183,7 +184,11 @@ pub fn topgrade_vagrant_box(ctx: &ExecutionContext, vagrant_box: &VagrantBox) ->
let mut _poweron = None; let mut _poweron = None;
if !vagrant_box.initial_status.powered_on() { if !vagrant_box.initial_status.powered_on() {
if !(ctx.config().vagrant_power_on().unwrap_or(true)) { if !(ctx.config().vagrant_power_on().unwrap_or(true)) {
return Err(SkipStep(format!("Skipping powered off box {vagrant_box}")).into()); return Err(SkipStep(format!(
"{}",
t!("Skipping powered off box {vagrant_box}", vagrant_box = vagrant_box)
))
.into());
} else { } else {
print_separator(seperator); print_separator(seperator);
_poweron = Some(vagrant.temporary_power_on(vagrant_box, ctx)?); _poweron = Some(vagrant.temporary_power_on(vagrant_box, ctx)?);
@@ -205,7 +210,7 @@ pub fn topgrade_vagrant_box(ctx: &ExecutionContext, vagrant_box: &VagrantBox) ->
pub fn upgrade_vagrant_boxes(ctx: &ExecutionContext) -> Result<()> { pub fn upgrade_vagrant_boxes(ctx: &ExecutionContext) -> Result<()> {
let vagrant = utils::require("vagrant")?; let vagrant = utils::require("vagrant")?;
print_separator("Vagrant boxes"); print_separator(t!("Vagrant boxes"));
let outdated = Command::new(&vagrant) let outdated = Command::new(&vagrant)
.args(["box", "outdated", "--global"]) .args(["box", "outdated", "--global"])
@@ -227,7 +232,7 @@ pub fn upgrade_vagrant_boxes(ctx: &ExecutionContext) -> Result<()> {
} }
if !found { if !found {
println!("No outdated boxes") println!("{}", t!("No outdated boxes"))
} else { } else {
ctx.run_type() ctx.run_type()
.execute(&vagrant) .execute(&vagrant)

View File

@@ -16,6 +16,7 @@ use crate::{
utils::{which, PathExt}, utils::{which, PathExt},
}; };
use rust_i18n::t;
#[cfg(unix)] #[cfg(unix)]
use std::os::unix::process::CommandExt as _; use std::os::unix::process::CommandExt as _;
@@ -158,7 +159,7 @@ pub fn run_in_tmux(config: TmuxConfig) -> Result<()> {
TmuxSessionMode::AttachIfNotInSession => { TmuxSessionMode::AttachIfNotInSession => {
if is_inside_tmux { if is_inside_tmux {
// Only attach to the newly-created session if we're not currently in a tmux session. // Only attach to the newly-created session if we're not currently in a tmux session.
println!("Topgrade launched in a new tmux session"); println!("{}", t!("Topgrade launched in a new tmux session"));
return Ok(()); return Ok(());
} else { } else {
tmux.build().args(["attach-client", "-t", &session]).exec() tmux.build().args(["attach-client", "-t", &session]).exec()

View File

@@ -10,6 +10,7 @@ use crate::{
execution_context::ExecutionContext, execution_context::ExecutionContext,
utils::{require, PathExt}, utils::{require, PathExt},
}; };
use rust_i18n::t;
use std::path::PathBuf; use std::path::PathBuf;
use std::{ use std::{
io::{self, Write}, io::{self, Write},
@@ -64,7 +65,7 @@ fn upgrade(command: &mut Executor, ctx: &ExecutionContext) -> Result<()> {
if !status.success() { if !status.success() {
return Err(TopgradeError::ProcessFailed(command.get_program(), status).into()); return Err(TopgradeError::ProcessFailed(command.get_program(), status).into());
} else { } else {
println!("Plugins upgraded") println!("{}", t!("Plugins upgraded"))
} }
} }
@@ -77,7 +78,7 @@ pub fn upgrade_ultimate_vimrc(ctx: &ExecutionContext) -> Result<()> {
let python = require("python3")?; let python = require("python3")?;
let update_plugins = config_dir.join("update_plugins.py").require()?; let update_plugins = config_dir.join("update_plugins.py").require()?;
print_separator("The Ultimate vimrc"); print_separator(t!("The Ultimate vimrc"));
ctx.run_type() ctx.run_type()
.execute(&git) .execute(&git)
@@ -108,7 +109,7 @@ pub fn upgrade_vim(ctx: &ExecutionContext) -> Result<()> {
let output = Command::new(&vim).arg("--version").output_checked_utf8()?; let output = Command::new(&vim).arg("--version").output_checked_utf8()?;
if !output.stdout.starts_with("VIM") { if !output.stdout.starts_with("VIM") {
return Err(SkipStep(String::from("vim binary might be actually nvim")).into()); return Err(SkipStep(t!("vim binary might be actually nvim").to_string()).into());
} }
let vimrc = vimrc()?; let vimrc = vimrc()?;

View File

@@ -210,8 +210,7 @@ pub fn run_oh_my_zsh(ctx: &ExecutionContext) -> Result<()> {
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
let default_path = oh_my_zsh.join("custom"); let default_path = oh_my_zsh.join("custom");
debug!( debug!(
"Running zsh returned {}. Using default path: {}", "Running zsh returned {e}. Using default path: {}",
e,
default_path.display() default_path.display()
); );
default_path default_path

View File

@@ -11,6 +11,7 @@ use color_eyre::eyre::Context;
use console::{style, Key, Term}; use console::{style, Key, Term};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use notify_rust::{Notification, Timeout}; use notify_rust::{Notification, Timeout};
use rust_i18n::t;
use tracing::{debug, error}; use tracing::{debug, error};
#[cfg(windows)] #[cfg(windows)]
use which_crate::which; use which_crate::which;
@@ -144,7 +145,7 @@ impl Terminal {
self.term self.term
.write_fmt(format_args!( .write_fmt(format_args!(
"{} {}", "{} {}",
style(format!("{key} failed:")).red().bold(), style(format!("{}", t!("{key} failed:", key = key))).red().bold(),
message message
)) ))
.ok(); .ok();
@@ -174,10 +175,10 @@ impl Terminal {
"{}: {}\n", "{}: {}\n",
key, key,
match result { match result {
StepResult::Success => format!("{}", style("OK").bold().green()), StepResult::Success => format!("{}", style(t!("OK")).bold().green()),
StepResult::Failure => format!("{}", style("FAILED").bold().red()), StepResult::Failure => format!("{}", style(t!("FAILED")).bold().red()),
StepResult::Ignored => format!("{}", style("IGNORED").bold().yellow()), StepResult::Ignored => format!("{}", style(t!("IGNORED")).bold().yellow()),
StepResult::Skipped(reason) => format!("{}: {}", style("SKIPPED").bold().blue(), reason), StepResult::Skipped(reason) => format!("{}: {}", style(t!("SKIPPED")).bold().blue(), reason),
} }
)) ))
.ok(); .ok();
@@ -188,7 +189,7 @@ impl Terminal {
self.term self.term
.write_fmt(format_args!( .write_fmt(format_args!(
"{}", "{}",
style(format!("{question} (y)es/(N)o",)).yellow().bold() style(format!("{question} {}", t!("(Y)es/(N)o"))).yellow().bold()
)) ))
.ok(); .ok();
@@ -207,14 +208,14 @@ impl Terminal {
} }
if self.set_title { if self.set_title {
self.term.set_title("Topgrade - Awaiting user"); self.term.set_title(format!("Topgrade - {}", t!("Awaiting user")));
} }
if self.desktop_notification { if self.desktop_notification {
self.notify_desktop(format!("{step_name} failed"), None); self.notify_desktop(format!("{}", t!("{step_name} failed", step_name = step_name)), None);
} }
let prompt_inner = style(format!("{}Retry? (y)es/(N)o/(s)hell/(q)uit", self.prefix)) let prompt_inner = style(format!("{}{}", self.prefix, t!("Retry? (y)es/(N)o/(s)hell/(q)uit")))
.yellow() .yellow()
.bold(); .bold();
@@ -224,7 +225,10 @@ impl Terminal {
match self.term.read_key() { match self.term.read_key() {
Ok(Key::Char('y')) | Ok(Key::Char('Y')) => break Ok(true), Ok(Key::Char('y')) | Ok(Key::Char('Y')) => break Ok(true),
Ok(Key::Char('s')) | Ok(Key::Char('S')) => { Ok(Key::Char('s')) | Ok(Key::Char('S')) => {
println!("\n\nDropping you to shell. Fix what you need and then exit the shell.\n"); println!(
"\n\n{}\n",
t!("Dropping you to shell. Fix what you need and then exit the shell.")
);
if let Err(err) = run_shell().context("Failed to run shell") { if let Err(err) = run_shell().context("Failed to run shell") {
self.term.write_fmt(format_args!("{err:?}\n{prompt_inner}")).ok(); self.term.write_fmt(format_args!("{err:?}\n{prompt_inner}")).ok();
} else { } else {

View File

@@ -5,6 +5,7 @@ use std::path::{Path, PathBuf};
use std::process::Command; use std::process::Command;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use rust_i18n::t;
use tracing::{debug, error}; use tracing::{debug, error};
use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::layer::SubscriberExt;
@@ -51,7 +52,11 @@ where
debug!("Path {:?} exists", self.as_ref()); debug!("Path {:?} exists", self.as_ref());
Ok(self) Ok(self)
} else { } else {
Err(SkipStep(format!("Path {:?} doesn't exist", self.as_ref())).into()) Err(SkipStep(format!(
"{}",
t!("Path {path} doesn't exist", path = format!("{:?}", self.as_ref()))
))
.into())
} }
} }
} }
@@ -92,9 +97,14 @@ pub fn require<T: AsRef<OsStr> + Debug>(binary_name: T) -> Result<PathBuf> {
Ok(path) Ok(path)
} }
Err(e) => match e { Err(e) => match e {
which_crate::Error::CannotFindBinaryPath => { which_crate::Error::CannotFindBinaryPath => Err(SkipStep(format!(
Err(SkipStep(format!("Cannot find {:?} in PATH", &binary_name)).into()) "{}",
} t!(
"Cannot find {binary_name} in PATH",
binary_name = format!("{:?}", &binary_name)
)
))
.into()),
_ => { _ => {
panic!("Detecting {:?} failed: {}", &binary_name, e); panic!("Detecting {:?} failed: {}", &binary_name, e);
} }
@@ -123,7 +133,7 @@ pub fn hostname() -> Result<String> {
match nix::unistd::gethostname() { match nix::unistd::gethostname() {
Ok(os_str) => Ok(os_str Ok(os_str) => Ok(os_str
.into_string() .into_string()
.map_err(|_| SkipStep("Failed to get a UTF-8 encoded hostname".into()))?), .map_err(|_| SkipStep(t!("Failed to get a UTF-8 encoded hostname").into()))?),
Err(e) => Err(e.into()), Err(e) => Err(e.into()),
} }
} }
@@ -132,7 +142,7 @@ pub fn hostname() -> Result<String> {
pub fn hostname() -> Result<String> { pub fn hostname() -> Result<String> {
Command::new("hostname") Command::new("hostname")
.output_checked_utf8() .output_checked_utf8()
.map_err(|err| SkipStep(format!("Failed to get hostname: {err}")).into()) .map_err(|err| SkipStep(t!("Failed to get hostname: {err}", err = err).to_string()).into())
.map(|output| output.stdout.trim().to_owned()) .map(|output| output.stdout.trim().to_owned())
} }
@@ -191,7 +201,9 @@ pub mod merge_strategies {
// Skip causes // Skip causes
// TODO: Put them in a better place when we have more of them // TODO: Put them in a better place when we have more of them
pub const REQUIRE_SUDO: &str = "Require sudo or counterpart but not found, skip"; pub fn get_require_sudo_string() -> String {
t!("Require sudo or counterpart but not found, skip").to_string()
}
/// Return `Err(SkipStep)` if `python` is a Python 2 or shim. /// Return `Err(SkipStep)` if `python` is a Python 2 or shim.
/// ///
@@ -218,11 +230,11 @@ pub fn check_is_python_2_or_shim(python: PathBuf) -> Result<PathBuf> {
.parse::<u32>() .parse::<u32>()
.expect("Major version should be a valid number"); .expect("Major version should be a valid number");
if major_version == 2 { if major_version == 2 {
return Err(SkipStep(format!("{} is a Python 2, skip.", python.display())).into()); return Err(SkipStep(t!("{python} is a Python 2, skip.", python = python.display()).to_string()).into());
} }
} else { } else {
// No version number, is a shim // No version number, is a shim
return Err(SkipStep(format!("{} is a Python shim, skip.", python.display())).into()); return Err(SkipStep(t!("{python} is a Python shim, skip.", python = python.display()).to_string()).into());
} }
Ok(python) Ok(python)