Merge pull request #19 from crystal-linux/development

v3.6.0 Development -> Main
i18n
Michal 2 years ago committed by GitHub
commit 00c9dd223c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,7 @@
FROM ghcr.io/crystal-linux/crystal:latest
RUN pacman -S --needed --noconfirm cargo openssl git binutils fakeroot pacman-contrib vim expac less
RUN useradd -m -G wheel vscode
RUN bash -c "echo \"vscode ALL=(ALL) NOPASSWD: ALL\" >> /etc/sudoers"
ENV USER=vscode

@ -0,0 +1,25 @@
{
"name": "Amethyst",
"build": {
"dockerfile": "Dockerfile",
},
"containerEnv": {
"PROJECT_DIR": "${containerWorkspaceFolder}"
},
"settings": {
"terminal.integrated.shell.linux": "/bin/bash"
},
"extensions": [
"vadimcn.vscode-lldb",
"mutantdino.resourcemonitor",
"matklad.rust-analyzer",
"tamasfe.even-better-toml",
"serayuzgur.crates"
],
"remoteUser": "vscode",
}

320
Cargo.lock generated

@ -4,10 +4,13 @@ version = 3
[[package]]
name = "Amethyst"
version = "3.5.0"
version = "3.6.0"
dependencies = [
"chrono",
"clap",
"clap_complete",
"colored",
"crossterm",
"libc",
"mimalloc",
"native-tls",
@ -15,9 +18,29 @@ dependencies = [
"rm_rf",
"serde",
"spinoff",
"textwrap",
"toml",
"ureq",
]
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "android_system_properties"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e"
dependencies = [
"libc",
]
[[package]]
name = "atty"
version = "0.2.14"
@ -47,6 +70,12 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bumpalo"
version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
[[package]]
name = "cc"
version = "1.0.73"
@ -59,6 +88,20 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
dependencies = [
"iana-time-zone",
"js-sys",
"num-integer",
"num-traits",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "chunked_transfer"
version = "1.4.0"
@ -83,6 +126,15 @@ dependencies = [
"textwrap",
]
[[package]]
name = "clap_complete"
version = "3.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4179da71abd56c26b54dd0c248cc081c1f43b0a1a7e8448e28e57a29baa993d"
dependencies = [
"clap",
]
[[package]]
name = "clap_derive"
version = "3.2.17"
@ -132,6 +184,31 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "crossterm"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
dependencies = [
"bitflags",
"crossterm_winapi",
"libc",
"mio",
"parking_lot",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
dependencies = [
"winapi",
]
[[package]]
name = "fastrand"
version = "1.8.0"
@ -187,6 +264,19 @@ dependencies = [
"libc",
]
[[package]]
name = "iana-time-zone"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad2bfd338099682614d3ee3fe0cd72e0b6a41ca6a87f6a74a3bd593c91650501"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"js-sys",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "idna"
version = "0.2.3"
@ -223,6 +313,15 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
[[package]]
name = "js-sys"
version = "0.3.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -244,6 +343,16 @@ dependencies = [
"cc",
]
[[package]]
name = "lock_api"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.17"
@ -265,6 +374,12 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "mimalloc"
version = "0.1.29"
@ -274,6 +389,18 @@ dependencies = [
"libmimalloc-sys",
]
[[package]]
name = "mio"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys",
]
[[package]]
name = "native-tls"
version = "0.2.10"
@ -292,6 +419,25 @@ dependencies = [
"tempfile",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.13.1"
@ -349,6 +495,29 @@ version = "6.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-sys",
]
[[package]]
name = "percent-encoding"
version = "2.1.0"
@ -427,6 +596,8 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
@ -476,6 +647,12 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "security-framework"
version = "2.7.0"
@ -501,18 +678,18 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.143"
version = "1.0.144"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553"
checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.143"
version = "1.0.144"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391"
checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
dependencies = [
"proc-macro2",
"quote",
@ -521,15 +698,57 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.83"
version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7"
checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "signal-hook"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
dependencies = [
"libc",
"mio",
"signal-hook",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
dependencies = [
"libc",
]
[[package]]
name = "smallvec"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
[[package]]
name = "smawk"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
[[package]]
name = "spinoff"
version = "0.5.3"
@ -633,7 +852,10 @@ version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
dependencies = [
"smawk",
"terminal_size",
"unicode-linebreak",
"unicode-width",
]
[[package]]
@ -651,6 +873,15 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "toml"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
dependencies = [
"serde",
]
[[package]]
name = "unicode-bidi"
version = "0.3.8"
@ -663,6 +894,15 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
[[package]]
name = "unicode-linebreak"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f"
dependencies = [
"regex",
]
[[package]]
name = "unicode-normalization"
version = "0.1.21"
@ -672,6 +912,12 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-width"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "ureq"
version = "2.5.0"
@ -712,6 +958,66 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a"
[[package]]
name = "winapi"
version = "0.3.9"

@ -1,10 +1,13 @@
[package]
name = "Amethyst"
version = "3.5.0"
version = "3.6.0"
authors = ["michal <michal@tar.black>", "axtlos <axtlos@tar.black>"]
edition = "2021"
description = "A fast and efficient AUR helper"
repository = "https://github.com/crystal-linux/amethyst"
license-file = "LICENSE.md"
keywords = ["aur", "crystal-linux", "pacman", "aur-helper"]
categories = ["command-line-utilities"]
default-run = "ame"
[[bin]]
@ -20,11 +23,16 @@ codegen-units = 1
[dependencies]
mimalloc = { version = "0.1.29", default-features = false }
clap = { version = "3.2.8", features = [ "derive", "wrap_help" ] }
clap_complete = "3.2.4"
regex = { version = "1.5.6", default-features = false, features = [ "std", "unicode-perl" ] }
colored = "2.0.0"
ureq = { version = "2.4.0", default-features = false, features = [ "native-tls", "json" ] }
serde = { version = "1.0.138", default-features = false, features = [ "derive", "serde_derive" ] }
serde = { version = "1.0.144", default-features = false, features = [ "derive", "serde_derive" ] }
native-tls = { version = "0.2.10", default-features = false }
libc = { version = "0.2.126", default-features = false }
rm_rf = { version = "0.6.2", default-features = false }
spinoff = { version = "0.5.3", default-features = false }
spinoff = { version = "0.5.2", default-features = false }
textwrap = { version = "0.15.0", features = [ "terminal_size", "smawk" ] }
chrono = { version = "0.4.22", default-features = false, features = [ "clock", "std", "wasmbind" ] }
toml = { version = "0.5.9", default-features = false }
crossterm = { version = "0.25.0", default-features = false }

@ -3,7 +3,7 @@
# Developer: Michal S <michal[at]tar[dot]black>
pkgname=amethyst
pkgver=3.5.0
pkgver=3.6.0
pkgrel=1
pkgdesc="A fast and efficient AUR helper"
arch=('x86_64')
@ -11,7 +11,7 @@ url="https://github.com/crystal-linux/amethyst"
license=('GPL3')
source=("git+$url")
sha256sums=('SKIP')
depends=('git' 'binutils' 'fakeroot' 'pacman-contrib' 'vim')
depends=('git' 'binutils' 'fakeroot' 'pacman-contrib' 'vim' 'expac' 'less')
makedepends=('cargo')
conflicts=('ame')

@ -8,14 +8,13 @@
<p align="center">
<a href="https://github.com/crystal-linux/amethyst"><img src="https://github.com/crystal-linux/amethyst/actions/workflows/test.yml/badge.svg"></a><br>
<a href="https://github.com/crystal-linux/.github/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-GPL--3.0-blue.svg" alt="License">
<a href="https://github.com/crystal-linux/.github/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-GPL--3.0-blue.svg" alt="License"></a>
<a href="https://github/crystal-linux/amethyst"><img alt="GitHub isses" src="https://img.shields.io/github/issues-raw/crystal-linux/amethyst"></a>
<a href="https://github/crystal-linux/amethyst"><img alt="GitHub pull requests" src="https://img.shields.io/github/issues-pr-raw/crystal-linux/amethyst"></a><br>
<a href="https://discord.gg/hYJgu8K5aA"><img alt="Discord" src="https://img.shields.io/discord/825473796227858482?color=blue&label=Discord&logo=Discord&logoColor=white"> </a>
<a href="https://github.com/ihatethefrench"> <img src="https://img.shields.io/badge/Maintainer-@not%2D-my%2D-segfault-brightgreen" alt=The maintainer of this repository" href="https://github.com/not-my-segfault"></a><br>
<a href="https://fosstodon.org/@crystal_linux"><img alt="Mastodon Follow" src="https://img.shields.io/mastodon/follow/108618426259408142?domain=https%3A%2F%2Ffosstodon.org">
<a href="https://github.com/not-my-segfault"><img src="https://img.shields.io/badge/Maintainer-@not%2D-my%2D-segfault-brightgreen" alt="The maintainer of this repository" href="https://github.com/not-my-segfault"></a><br>
<a href="https://fosstodon.org/@crystal_linux"><img alt="Mastodon Follow" src="https://img.shields.io/mastodon/follow/108618426259408142?domain=https%3A%2F%2Ffosstodon.org"></a>
<a href="https://twitter.com/crystal_linux"><img alt="Twitter Follow" src="https://img.shields.io/twitter/follow/crystal_linux"></a>
</a>
</p>
<p align="center">
@ -25,30 +24,34 @@ Made for Crystal, compatible with any Arch-based Linux distribution.
### Basic usage
| Action | FreeBSD pkg-style alias | Pacman-style flags |
|----------------------|-------------------------|--------------------|
| Install a package | ame ins/install | ame -S |
| Remove a package | ame rm/remove | ame -R/-Rs |
| Upgrade a package | ame upg/upgrade | ame -Syu |
| Search for a package | ame sea | ame -Ss |
| Action | FreeBSD pkg-style alias | Pacman-style flags |
|------------------------|-------------------------|--------------------|
| Install a package | ame ins/install | ame -S |
| Remove a package | ame rm/remove | ame -R/-Rs |
| Upgrade a package | ame upg/upgrade | ame -Syu |
| Search for a package | ame sea/search | ame -Ss |
| Query the package list | ame qu/query | ame -Q |
| Show a package's info | ame inf/info | ame -Qi |
| Clean the pacman cache | ame cl/clean | ame -Sc |
| Check for .pacnew | ame di/diff | ame -D |
### Exit codes overview
| Exit Code (i32) | Reason |
|-----------------|----------------------------------------------------------|
| 1 | Running ame as UID 0 / root |
| 2 | Failed adding package to database |
| 3 | Failed initialising database |
| 4 | Error creating cache and/or database paths |
| 5 | Could not find one or more required package dependencies |
| 6 | User cancelled package installation |
| 7 | Pacman error when installing package |
| 8 | Git error |
| 9 | makepkg error |
| 2 | Failed creating paths |
| 3 | Could not find one or more required package dependencies |
| 4 | User cancelled package installation |
| 5 | Pacman error when installing package |
| 6 | Git error |
| 7 | Makepkg error |
| 8 | Failed to parse config file |
| 63 | Any other misc error |
### How to build:
Tested on latest Cargo (1.60.0-nightly)
Tested on latest Cargo (1.62.0-nightly)
<br>
@ -60,14 +63,10 @@ Tested on latest Cargo (1.60.0-nightly)
- `cargo build --release`
#### Pkg-warner included
- `cargo build (--release) --all --features=pkg-warner`
### TODO:
#### v3.6.0
- Allow editing of PKGBUILDs before install
- ~~Allow editing of PKGBUILDs before install~~
<!--

@ -1,7 +1,9 @@
use clap::{Parser, Subcommand};
#![allow(clippy::module_name_repetitions)]
use clap::{Parser, Subcommand, ValueHint};
#[derive(Debug, Clone, Parser)]
#[clap(name = "Amethyst", version = env ! ("CARGO_PKG_VERSION"), about = env ! ("CARGO_PKG_DESCRIPTION"))]
#[clap(bin_name = "ame", name = "Amethyst", version = env ! ("CARGO_PKG_VERSION"), about = env ! ("CARGO_PKG_DESCRIPTION"), infer_subcommands = true, allow_external_subcommands = true, allow_hyphen_values = true)]
pub struct Args {
#[clap(subcommand)]
pub subcommand: Option<Operation>,
@ -17,40 +19,48 @@ pub struct Args {
/// Loops sudo in the background to ensure it doesn't time out during long builds
#[clap(long = "sudoloop", global = true)]
pub sudoloop: bool,
/// Sets a custom AUR clone and build directory for the specified operation
#[clap(long, short, global = true, value_hint = ValueHint::DirPath)]
pub cachedir: Option<String>,
}
#[derive(Debug, Clone, Subcommand)]
pub enum Operation {
/// Installs a package from either the AUR or the PacMan-defined repositories
#[clap(name = "install", aliases = & ["ins", "in", "i", "-S"])]
/// Installs a package from either the AUR or the Pacman-defined repositories
#[clap(bin_name = "ame", name = "install", visible_aliases = & ["-S"], aliases = & ["-Sa", "-Sr"])]
Install(InstallArgs),
/// Removes a previously installed package
#[clap(name = "remove", aliases = & ["rm", "rem", "r", "-R", "-Rs"])]
#[clap(bin_name = "ame", name = "remove", visible_aliases = & ["rm", "-Rs"])]
Remove(RemoveArgs),
/// Searches for the relevant packages in both the AUR and repos
#[clap(name = "search", aliases = & ["sea", "sear", "se", "s", "-Ss"])]
/// Searches for packages matching a regex-supported pattern in the AUR and/or the repos
#[clap(bin_name = "ame", name = "search", visible_aliases = & ["-Ss"], aliases = & ["-Ssa", "-Ssr"])]
Search(SearchArgs),
/// Queries installed packages
#[clap(name = "query", aliases = & ["q", "qu", "l", "ls", "-Q"])]
#[clap(bin_name = "ame", name = "query", visible_aliases = & ["-Q"], aliases = & ["-Qa", "-Qr", "-Qm", "-Qn"])]
Query(QueryArgs),
/// Gets info about a package
#[clap(name = "info", aliases = & ["inf", "in", "i", "-Qi"])]
#[clap(bin_name = "ame", name = "info", visible_aliases = & ["-Qi"])]
Info(InfoArgs),
/// Upgrades locally installed packages to their latest versions
#[clap(name = "upgrade", aliases = & ["upg", "up", "u", "-Syu"])]
/// Upgrades locally installed packages to their latest versions (Default)
#[clap(bin_name = "ame", name = "upgrade", visible_aliases = & ["-Syu"])]
Upgrade(UpgradeArgs),
/// Generates shell completions for supported shells (bash, fish, elvish, pwsh)
#[clap(bin_name = "ame", name = "gencomp", visible_aliases = & ["-g"])]
GenComp(GenCompArgs),
/// Removes all orphaned packages
#[clap(name = "clean", aliases = & ["cln", "cl", "-Sc"])]
#[clap(bin_name = "ame", name = "clean", visible_aliases = & ["-Sc"])]
Clean,
/// Runs pacdiff
#[clap(name = "diff", aliases = & ["dif", "di", "-d"])]
#[clap(bin_name = "ame", name = "diff", visible_aliases = & ["-d"])]
Diff,
}
@ -66,11 +76,11 @@ pub struct InstallArgs {
#[clap(required = true)]
pub packages: Vec<String>,
/// Installs only from the AUR
/// Installs only from the AUR [-Sa]
#[clap(long, short)]
pub aur: bool,
/// Install the packages from the pacman-defined repositories
/// Install the packages only from the pacman-defined repositories [-Sr]
#[clap(long, short)]
pub repo: bool,
}
@ -84,11 +94,11 @@ pub struct RemoveArgs {
#[derive(Default, Debug, Clone, Parser)]
pub struct SearchArgs {
/// Searches for the relevant packages in both the AUR and repos
/// Searches for the relevant packages in both the AUR and repos [-Ssa]
#[clap(long, short)]
pub aur: bool,
/// Searches only local repos for the package
/// Searches only pacman repos for the package [-Ssr]
#[clap(long, short)]
pub repo: bool,
@ -99,12 +109,12 @@ pub struct SearchArgs {
#[derive(Default, Debug, Clone, Parser)]
pub struct QueryArgs {
/// Lists AUR/foreign packages
#[clap(long, short, from_global)]
/// Lists AUR/foreign packages [-Qa, -Qm]
#[clap(long, short)]
pub aur: bool,
/// Lists repo/native packages
#[clap(long, short, from_global)]
/// Lists repo/native packages [-Qr, -Qn]
#[clap(long, short)]
pub repo: bool,
}
@ -125,3 +135,10 @@ pub struct UpgradeArgs {
#[clap(long, short)]
pub aur: bool,
}
#[derive(Default, Debug, Clone, Parser)]
pub struct GenCompArgs {
/// The shell to generate completions for (bash, fish, elvish, pwsh)
#[clap(required = true)]
pub shell: String,
}

@ -2,6 +2,7 @@ use regex::Regex;
use crate::{log, Options};
/// Strips packages from versioning and other extraneous information.
pub fn clean(a: &[String], options: Options) -> Vec<String> {
// Strip versioning from package names
let r = Regex::new(r"(\S+)((?:>=|<=|>|<|=\W)\S+$)").unwrap();

@ -1,6 +1,8 @@
use std::ffi::{OsStr, OsString};
use std::fs;
use std::process::{Child, Command, ExitStatus, Stdio};
use crate::internal::config;
use crate::internal::error::{AppError, AppResult};
use crate::internal::is_tty;
@ -10,7 +12,7 @@ pub struct StringOutput {
pub status: ExitStatus,
}
/// A wrapper around [std::process::Command] with predefined
/// A wrapper around [`std::process::Command`] with predefined
/// commands used in this project as well as elevated access.
pub struct ShellCommand {
command: String,
@ -20,7 +22,12 @@ pub struct ShellCommand {
impl ShellCommand {
pub fn pacman() -> Self {
let pacman_cmd = Self::new("pacman");
let config = config::read();
let pacman_cmd = if config.base.powerpill && fs::metadata("/usr/bin/powerpill").is_ok() {
Self::new("powerpill")
} else {
Self::new("pacman")
};
if is_tty() {
pacman_cmd.arg("--color=always")
@ -49,7 +56,7 @@ impl ShellCommand {
Self::new("sudo")
}
fn new<S: ToString>(command: S) -> Self {
fn new(command: &str) -> Self {
Self {
command: command.to_string(),
args: Vec::new(),
@ -77,7 +84,7 @@ impl ShellCommand {
}
/// Runs the command with sudo
pub fn elevated(mut self) -> Self {
pub const fn elevated(mut self) -> Self {
self.elevated = true;
self

@ -0,0 +1,65 @@
#![allow(clippy::module_name_repetitions)]
use serde::Deserialize;
use std::{env, fs};
use crate::{crash, AppExitCode};
#[derive(Debug, Deserialize)]
pub struct Config {
pub base: ConfigBase,
pub extra: ConfigExtra,
}
#[derive(Debug, Deserialize)]
pub struct ConfigBase {
pub pacdiff_warn: bool,
pub highlight_optdepends: bool,
pub powerpill: bool,
}
#[derive(Debug, Deserialize)]
pub struct ConfigExtra {
pub uwu: Option<bool>,
pub uwu_debug: Option<bool>,
pub review_user_shell: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
base: ConfigBase {
pacdiff_warn: true,
highlight_optdepends: true,
powerpill: false,
},
extra: ConfigExtra {
uwu: None,
uwu_debug: None,
review_user_shell: false,
},
}
}
}
pub fn read() -> Config {
let file = fs::read_to_string(format!(
"{}/{}",
env::var("HOME").unwrap(),
".config/ame/config.toml"
))
.unwrap_or_else(|e| {
crash!(
AppExitCode::ConfigParseError,
"Couldn't find config file: {}",
e
);
});
toml::from_str(&file).unwrap_or_else(|e| {
crash!(
AppExitCode::ConfigParseError,
"Could not parse config file: {}",
e
);
})
}

@ -1,10 +1,10 @@
use std::env;
use crate::internal::commands::ShellCommand;
use crate::internal::config;
use crate::internal::error::SilentUnwrap;
use crate::internal::exit_code::AppExitCode;
use crate::{prompt, spinner, warn};
/// Searches the filesystem for .pacnew files and helps the user deal with them.
pub fn detect() {
// Start spinner
let sp = spinner!("Scanning for pacnew files");
@ -25,14 +25,22 @@ pub fn detect() {
}
// If pacnew files are found, warn the user and prompt to pacdiff
if !pacnew.is_empty() {
sp.stop_bold("It appears that at least one program you have installed / upgraded has installed a .pacnew/.pacsave config file. These are created when you have modified a program's configuration, and a package upgrade could not automatically merge the new file.");
if pacnew.is_empty() {
sp.stop_bold("No pacnew files found");
} else {
sp.stop_bold("It appears that at least one program you have installed / upgraded has installed a .pacnew config file. These are created when you have modified a program's configuration, and a package upgrade could not automatically merge the new file.");
let choice = prompt!(default false, "Would you like to run pacdiff to deal with this? You can always deal with this later by running `sudo pacdiff`");
let choice = prompt!(default false, "Would you like Amethyst to run pacdiff to deal with this? You can always deal with this later by running `sudo pacdiff`");
if choice {
if env::var("PACDIFF_WARNING").unwrap_or_else(|_| "1".to_string()) != "0" {
let config = config::read();
if config.base.pacdiff_warn {
ShellCommand::pacdiff()
.elevated()
.wait()
.silent_unwrap(AppExitCode::PacmanError);
} else {
warn!("Pacdiff uses vimdiff by default to edit files for merging. You can focus panes by mousing over them and pressing left click, and scroll up and down using your mouse's scroll wheel (or the arrow keys). To exit vimdiff, press the following key combination: ESC, :qa!, ENTER");
warn!("You can surpress this warning in the future by setting the `PACDIFF_WARNING` environment variable to `0`");
warn!("You can surpress this warning in the future by setting `pacdiff_warn` to \"false\" in ~/.config/ame/config.toml");
let cont = prompt!(default false, "Continue?");
if cont {
ShellCommand::pacdiff()
@ -40,14 +48,7 @@ pub fn detect() {
.wait()
.silent_unwrap(AppExitCode::PacmanError);
}
} else {
ShellCommand::pacdiff()
.elevated()
.wait()
.silent_unwrap(AppExitCode::PacmanError);
}
}
} else {
sp.stop_bold("No pacnew files found");
}
}

@ -8,6 +8,7 @@ use crate::internal::exit_code::AppExitCode;
pub type AppResult<T> = Result<T, AppError>;
#[derive(Debug)]
#[allow(clippy::module_name_repetitions)]
pub enum AppError {
Io(std::io::Error),
Other(String),
@ -17,9 +18,9 @@ pub enum AppError {
impl Display for AppError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
AppError::Io(io) => Display::fmt(io, f),
AppError::Other(s) => Display::fmt(s, f),
AppError::NonZeroExit => Display::fmt("exited with non zero code", f),
Self::Io(io) => Display::fmt(io, f),
Self::Other(s) => Display::fmt(s, f),
Self::NonZeroExit => Display::fmt("Exited with non-zero exit code", f),
}
}
}
@ -52,7 +53,7 @@ impl<T> SilentUnwrap<T> for AppResult<T> {
fn silent_unwrap(self, exit_code: AppExitCode) -> T {
match self {
Ok(val) => val,
Err(_) => crash!(exit_code, "an error occurred"),
Err(_) => crash!(exit_code, "An error occurred"),
}
}
}

@ -1,3 +1,5 @@
#[allow(clippy::module_name_repetitions)]
/// Defined exit codes for the program
pub enum AppExitCode {
RunAsRoot = 1,
FailedCreatingPaths = 2,
@ -6,5 +8,6 @@ pub enum AppExitCode {
PacmanError = 5,
GitError = 6,
MakePkgError = 7,
ConfigParseError = 8,
Other = 63,
}

@ -3,46 +3,81 @@ use std::path::Path;
use crate::{crash, internal::exit_code::AppExitCode, log, Options};
/// Ensure all required directories and files exist.
pub fn init(options: Options) {
// Initialise variables
let verbosity = options.verbosity;
let homedir = env::var("HOME").unwrap();
// If cache path doesn't exist, create it, if it does, delete it and recreate it
if !Path::new(&format!("{}/.cache/ame/", homedir)).exists() {
// If stateful dir doesn't exist, create it
if !Path::new(&format!("{}/.local/share/ame/", homedir)).exists() {
if verbosity >= 1 {
log!("Initialising cache directory");
log!("Initialising stateful directory");
}
std::fs::create_dir_all(format!("{}/.cache/ame", homedir)).unwrap_or_else(|e| {
std::fs::create_dir_all(format!("{}/.local/share/ame", homedir)).unwrap_or_else(|e| {
crash!(
AppExitCode::FailedCreatingPaths,
"Couldn't create path: {}/.cache/ame: {}",
"Couldn't create path: {}/.local/share/ame: {}",
homedir,
e,
);
});
} else {
}
// If cache dir doesn't exist, create it
if !Path::new(&format!("{}/.cache/ame", homedir)).exists() {
if verbosity >= 1 {
log!("Deleting cache directory");
log!("Initialising cache directory");
}
rm_rf::remove(format!("{}/.cache/ame", homedir)).unwrap_or_else(|e| {
std::fs::create_dir_all(format!("{}/.cache/ame", homedir)).unwrap_or_else(|e| {
crash!(
AppExitCode::FailedCreatingPaths,
"Couldn't remove path: {}/.cache/ame: {}",
"Couldn't create path: {}/.cache/ame: {}",
homedir,
e
)
e,
);
});
}
// If config dir doesn't exist, create it
if !Path::new(&format!("{}/.config/ame/", homedir)).exists() {
if verbosity >= 1 {
log!("Creating cache directory");
log!("Initialising config directory");
}
std::fs::create_dir_all(format!("{}/.cache/ame", homedir)).unwrap_or_else(|e| {
std::fs::create_dir_all(format!("{}/.config/ame", homedir)).unwrap_or_else(|e| {
crash!(
AppExitCode::FailedCreatingPaths,
"Couldn't create path: {}/.cache/ame: {}",
"Couldn't create path: {}/.config/ame: {}",
homedir,
e
)
e,
);
});
}
// If config file doesn't exist, create it
let config = "\
[base]
pacdiff_warn = true
highlight_optdepends = true
powerpill = false
[extra]
review_user_shell = false
";
if !Path::new(&format!("{}/.config/ame/config.toml", homedir)).exists() {
if verbosity >= 1 {
log!("Initialising config file");
}
std::fs::write(format!("{}/.config/ame/config.toml", homedir), config).unwrap_or_else(
|e| {
crash!(
AppExitCode::FailedCreatingPaths,
"Couldn't create path: {}/.config/ame/config.toml: {}",
homedir,
e,
);
},
);
}
}

@ -5,11 +5,11 @@ pub use initialise::*;
pub use initialise::*;
pub use sort::*;
pub use sort::*;
use std::env;
pub use sudoloop::*;
mod clean;
pub mod commands;
pub mod config;
mod detect;
pub mod error;
pub mod exit_code;
@ -18,7 +18,7 @@ pub mod rpc;
mod sort;
pub mod structs;
#[macro_use]
pub(crate) mod utils;
pub mod utils;
mod sudoloop;
#[macro_export]
@ -39,11 +39,13 @@ macro_rules! uwu {
}
pub fn uwu_enabled() -> bool {
env::var("AME_UWU").unwrap_or_else(|_| "".to_string()) == "true"
let config = config::read();
config.extra.uwu.unwrap_or(false)
}
pub fn uwu_debug_enabled() -> bool {
env::var("AME_UWU_DEBUG").unwrap_or_else(|_| "".to_string()) == "true"
let config = config::read();
config.extra.uwu_debug.unwrap_or(false)
}
/// Checks if we're running in a tty. If we do we can assume that

@ -1,6 +1,7 @@
use std::sync::Arc;
#[derive(serde::Deserialize, Debug, Clone)]
/// Struct for deserializing RPC results.
pub struct Package {
#[serde(rename = "Name")]
pub name: String,
@ -17,15 +18,20 @@ pub struct Package {
#[serde(rename = "OptDepends")]
#[serde(default)]
pub opt_depends: Vec<String>,
#[serde(rename = "OutOfDate")]
#[serde(default)]
pub out_of_date: Option<usize>,
}
#[derive(serde::Deserialize)]
/// Struct for retreiving search results from the AUR.
pub struct SearchResults {
pub resultcount: u32,
pub results: Vec<Package>,
}
#[derive(Clone)]
/// Struct for retreiving package information from the AUR.
pub struct InfoResults {
pub found: bool,
pub package: Option<Package>,
@ -33,7 +39,8 @@ pub struct InfoResults {
pub const URL: &str = "https://aur.archlinux.org/";
pub fn rpcinfo(pkg: String) -> InfoResults {
/// Return a struct of type [`InfoResults`] from the AUR.
pub fn rpcinfo(pkg: &str) -> InfoResults {
// Initialise TLS connector
let tls_connector = Arc::new(native_tls::TlsConnector::new().unwrap());
@ -67,7 +74,8 @@ pub fn rpcinfo(pkg: String) -> InfoResults {
}
}
pub fn rpcsearch(pkg: String) -> SearchResults {
/// Return a struct of type [`SearchResults`] from the AUR.
pub fn rpcsearch(pkg: &str) -> SearchResults {
// Initialise TLS connector
let tls_connector = Arc::new(native_tls::TlsConnector::new().unwrap());

@ -3,6 +3,7 @@ use std::process::{Command, Stdio};
use crate::internal::{clean, rpc, structs};
use crate::{log, Options};
/// Sorts the given packages into an [`crate::internal::structs::Sorted`]
pub fn sort(input: &[String], options: Options) -> structs::Sorted {
// Initialise variables
let mut repo: Vec<String> = vec![];
@ -26,13 +27,13 @@ pub fn sort(input: &[String], options: Options) -> structs::Sorted {
.status()
.expect("Something has gone wrong");
if let Some(0) = rs.code() {
if rs.code() == Some(0) {
// If it is, add it to the repo vector
if verbosity >= 1 {
log!("{} found in repos", b);
}
repo.push(b.to_string());
} else if rpc::rpcinfo(b.to_string()).found {
} else if rpc::rpcinfo(&b).found {
// Otherwise, check if it is in the AUR, if it is, add it to the AUR vector
if verbosity >= 1 {
log!("{} found in AUR", b);

@ -1,4 +1,5 @@
#[derive(Debug, serde::Serialize)]
/// Struct for packages exiting [`crate::internal::sort()`].
pub struct Sorted {
#[allow(dead_code)]
pub repo: Vec<String>,
@ -10,14 +11,14 @@ pub struct Sorted {
impl Sorted {
pub fn new(repo: Vec<String>, aur: Vec<String>, nf: Vec<String>) -> Self {
let a: Sorted = Sorted { repo, aur, nf };
a
Self { repo, aur, nf }
}
}
#[derive(Clone, Copy)]
/// Options to be passed down to internal functions
pub struct Options {
pub verbosity: i32,
pub verbosity: usize,
pub noconfirm: bool,
pub asdeps: bool,
}

@ -3,12 +3,13 @@ use std::time::Duration;
use crate::ShellCommand;
/// Loop sudo so it doesn't time out
/// Loop sudo so longer builds don't time out
#[allow(clippy::module_name_repetitions)]
pub fn start_sudoloop() {
prompt_sudo();
std::thread::spawn(|| loop {
prompt_sudo();
thread::sleep(Duration::from_secs(3 * 60))
thread::sleep(Duration::from_secs(3 * 60));
});
}

@ -1,14 +1,15 @@
use colored::*;
use colored::Colorize;
use std::io;
use std::io::Write;
use std::process::exit;
use std::process::{exit, Command, Stdio};
use std::time::UNIX_EPOCH;
use textwrap::wrap;
use crate::internal::exit_code::AppExitCode;
use crate::{internal, uwu};
const OK_SYMBOL: &str = "❖";
const ERR_SYMBOL: &str = "";
const ERR_SYMBOL: &str = "X";
const WARN_SYMBOL: &str = "!";
const PROMPT_SYMBOL: &str = "?";
@ -16,6 +17,7 @@ const PROMPT_YN_DEFAULT_TRUE: &str = "[Y/n]";
const PROMPT_YN_DEFAULT_FALSE: &str = "[y/N]";
#[macro_export]
/// Macro for printing a message to stdout.
macro_rules! info {
($($arg:tt)+) => {
$crate::internal::utils::log_info(format!($($arg)+))
@ -23,6 +25,7 @@ macro_rules! info {
}
#[macro_export]
/// Macro for printing a warning message non-destructively.
macro_rules! warn {
($($arg:tt)+) => {
$crate::internal::utils::log_warn(format!($($arg)+))
@ -30,6 +33,7 @@ macro_rules! warn {
}
#[macro_export]
/// Macro for printing a message and destructively exiting
macro_rules! crash {
($exit_code:expr, $($arg:tt)+) => {
$crate::internal::utils::log_and_crash(format!($($arg)+), $exit_code)
@ -37,6 +41,7 @@ macro_rules! crash {
}
#[macro_export]
/// Macro for logging to stderr
macro_rules! log {
($($arg:tt)+) => {
$crate::internal::utils::log_debug(format!($($arg)+))
@ -44,6 +49,7 @@ macro_rules! log {
}
#[macro_export]
/// Macro for prompting the user with a yes/no question.
macro_rules! prompt {
(default $default:expr, $($arg:tt)+) => {
$crate::internal::utils::prompt_yn(format!($($arg)+), $default)
@ -51,48 +57,70 @@ macro_rules! prompt {
}
#[macro_export]
/// Macro for creating a spinner.
macro_rules! spinner {
($($arg:tt)+) => {
$crate::internal::utils::spinner_fn(format!($($arg)+))
}
}
pub fn log_info<S: ToString>(msg: S) {
let msg = msg.to_string();
/// Print a formatted message to stdout.
pub fn log_info(msg: String) {
let msg = if internal::uwu_enabled() {
uwu!(&msg)
} else {
msg
};
println!("{} {}", OK_SYMBOL.purple(), msg.bold())
let opts = textwrap::Options::new(crossterm::terminal::size().unwrap().0 as usize - 2)
.subsequent_indent(" ");
println!(
"{} {}",
OK_SYMBOL.purple(),
wrap(&msg, opts).join("\n").bold()
);
}
pub fn log_warn<S: ToString>(msg: S) {
let msg = msg.to_string();
/// Print a non-destructive warning message
pub fn log_warn(msg: String) {
let msg = if internal::uwu_enabled() {
uwu!(&msg)
} else {
msg
};
println!("{} {}", WARN_SYMBOL.yellow(), msg.bold())
let opts = textwrap::Options::new(crossterm::terminal::size().unwrap().0 as usize - 2)
.subsequent_indent(" ");
println!(
"{} {}",
WARN_SYMBOL.yellow(),
wrap(&msg, opts).join("\n").yellow().bold()
);
}
pub fn log_and_crash<S: ToString>(msg: S, exit_code: AppExitCode) -> ! {
let msg = msg.to_string();
/// Logs a message and exits the program with the given exit code.
pub fn log_and_crash(msg: String, exit_code: AppExitCode) -> ! {
let msg = if internal::uwu_enabled() {
uwu!(&msg)
} else {
msg
};
println!("{}: {}", ERR_SYMBOL.red().bold(), msg.red().bold());
let opts = textwrap::Options::new(crossterm::terminal::size().unwrap().0 as usize - 2)
.subsequent_indent(" ");
println!(
"{} {}",
ERR_SYMBOL.red().bold(),
wrap(&msg, opts).join("\n").red().bold()
);
exit(exit_code as i32);
}
pub fn log_debug<S: ToString>(msg: S) {
let msg = msg.to_string();
/// Logs a message to stderr with timestamp
pub fn log_debug(msg: String) {
let msg = if internal::uwu_enabled() && internal::uwu_debug_enabled() {
uwu!(&msg)
} else {
@ -109,9 +137,8 @@ pub fn log_debug<S: ToString>(msg: S) {
);
}
pub fn prompt_yn<S: ToString>(question: S, default_true: bool) -> bool {
let question = question.to_string();
/// Prompts the user for a yes/no answer.
pub fn prompt_yn(question: String, default_true: bool) -> bool {
let yn_prompt = if default_true {
PROMPT_YN_DEFAULT_TRUE
} else {
@ -124,17 +151,20 @@ pub fn prompt_yn<S: ToString>(question: S, default_true: bool) -> bool {
question
};
let opts = textwrap::Options::new(crossterm::terminal::size().unwrap().0 as usize - 2)
.subsequent_indent(" ");
print!(
"{} {} {}: ",
PROMPT_SYMBOL.purple(),
question.bold(),
wrap(&question, opts).join("\n").bold(),
yn_prompt
);
let mut yn: String = String::new();
io::stdout().flush().ok();
let _ = std::io::stdin().read_line(&mut yn);
io::stdin().read_line(&mut yn).unwrap();
if yn.trim().to_lowercase() == "n" || yn.trim().to_lowercase() == "no" {
false
@ -157,27 +187,53 @@ impl Spinner {
text.to_string()
};
let symbol = Box::new(format!("{}", OK_SYMBOL.purple()));
let text = Box::new(format!("{}", text.bold()));
let opts = textwrap::Options::new(crossterm::terminal::size().unwrap().0 as usize - 2)
.subsequent_indent(" ");
let symbol: &'static str = Box::leak(symbol);
let text: &'static str = Box::leak(text);
let symbol = format!("{}", OK_SYMBOL.purple());
let text = format!("{}", wrap(&text, opts).join("\n").bold());
self.spinner.stop_and_persist(symbol, text);
self.spinner.stop_and_persist(&symbol, &text);
}
}
/// Returns a spinner that can be used to display progress.
pub fn spinner_fn(text: String) -> Spinner {
let text = if internal::uwu_enabled() {
uwu!(&text)
} else {
text
};
let opts = textwrap::Options::new(crossterm::terminal::size().unwrap().0 as usize - 2)
.subsequent_indent(" ");
Spinner {
spinner: spinoff::Spinner::new(
spinoff::Spinners::Line,
format!("{}", text.bold()),
format!("{}", wrap(&text, opts).join("\n").bold()),
spinoff::Color::Magenta,
),
}
}
/// Opens a String in `less`.
pub fn pager(text: &String) -> io::Result<()> {
let text = if internal::uwu_enabled() {
uwu!(text)
} else {
text.to_string()
};
let mut pager = Command::new("less")
.arg("-R")
.stdin(Stdio::piped())
.spawn()?;
let stdin = pager.stdin.as_mut().unwrap();
stdin.write_all(text.as_bytes())?;
stdin.flush()?;
pager.wait()?;
Ok(())
}

@ -1,15 +1,21 @@
// #![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
// #![allow(clippy::too_many_lines)]
#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
#![allow(clippy::too_many_lines)]
use args::Args;
use clap::Parser;
use clap::{CommandFactory, Parser};
use clap_complete::{Generator, Shell};
use internal::commands::ShellCommand;
use internal::error::SilentUnwrap;
use std::env;
use std::fs;
use std::path::Path;
use std::str::FromStr;
use crate::args::{
InfoArgs, InstallArgs, Operation, QueryArgs, RemoveArgs, SearchArgs, UpgradeArgs,
GenCompArgs, InfoArgs, InstallArgs, Operation, QueryArgs, RemoveArgs, SearchArgs, UpgradeArgs,
};
use crate::internal::exit_code::AppExitCode;
use crate::internal::utils::pager;
use crate::internal::{detect, init, sort, start_sudoloop, structs::Options};
#[global_allocator]
@ -29,7 +35,7 @@ fn main() {
let args: Args = Args::parse();
// Initialize variables
let verbosity = args.verbose as i32;
let verbosity = args.verbose;
let noconfirm = args.no_confirm;
// Get options struct
@ -47,14 +53,60 @@ fn main() {
start_sudoloop();
}
let cachedir = if args.cachedir.is_none() {
"".to_string()
} else {
// Create cache directory if it doesn't exist
if fs::metadata(&args.cachedir.as_ref().unwrap()).is_err() {
fs::create_dir(&args.cachedir.as_ref().unwrap()).unwrap_or_else(|err| {
crash!(
AppExitCode::FailedCreatingPaths,
"Could not create cache directory: {}",
err
);
});
}
Path::new(&args.cachedir.unwrap())
.canonicalize()
.unwrap()
.to_str()
.unwrap()
.to_string()
};
// List of possible options
let opers = vec![
"install", "remove", "upgrade", "search", "query", "info", "clean", "diff", "gencomp",
];
// If arg is completely unrecognized, attempt to pass it to pacman
if let Some((ext, ext_m)) = args::Args::command().get_matches().subcommand() {
if !opers.contains(&ext) {
let mut m = ext_m
.values_of("")
.unwrap_or_default()
.collect::<Vec<&str>>();
m.insert(0, ext);
info!("Passing unrecognized flags \"{}\" to pacman", m.join(" "));
let child = ShellCommand::pacman()
.args(m)
.elevated()
.wait()
.silent_unwrap(AppExitCode::PacmanError);
std::process::exit(child.code().unwrap_or(1));
}
}
// Match args
match args.subcommand.unwrap_or_default() {
Operation::Install(install_args) => cmd_install(install_args, options),
Operation::Install(install_args) => cmd_install(install_args, options, &cachedir),
Operation::Remove(remove_args) => cmd_remove(remove_args, options),
Operation::Search(search_args) => cmd_search(search_args, options),
Operation::Query(query_args) => cmd_query(query_args),
Operation::Search(search_args) => cmd_search(&search_args, options),
Operation::Query(query_args) => cmd_query(&query_args),
Operation::Info(info_args) => cmd_info(info_args),
Operation::Upgrade(upgrade_args) => cmd_upgrade(upgrade_args, options),
Operation::Upgrade(upgrade_args) => cmd_upgrade(upgrade_args, options, &cachedir),
Operation::Clean => {
info!("Removing orphaned packages");
operations::clean(options);
@ -63,13 +115,26 @@ fn main() {
info!("Running pacdiff");
detect();
}
Operation::GenComp(gencomp_args) => {
info!("Generating shell completions for {}. Please pipe `stderr` to a file to get completions as a file, e.g. `ame gencomp fish 2> file.fish`", gencomp_args.shell);
cmd_gencomp(&gencomp_args);
}
}
}
fn cmd_install(args: InstallArgs, options: Options) {
fn cmd_install(args: InstallArgs, options: Options, cachedir: &str) {
// Initialise variables
let packages = args.packages;
if args.aur && args.repo {
crash!(AppExitCode::Other, "Cannot specify both --aur and --repo");
}
let aur = args.aur || env::args().collect::<Vec<String>>()[1] == "-Sa";
let repo = args.repo || env::args().collect::<Vec<String>>()[1] == "-Sr";
let sorted = sort(&packages, options);
let config = internal::config::read();
info!("Attempting to install packages: {}", packages.join(", "));
@ -82,47 +147,30 @@ fn cmd_install(args: InstallArgs, options: Options) {
);
}
if !sorted.repo.is_empty() {
if !repo && !aur && !sorted.repo.is_empty() || repo && !sorted.repo.is_empty() {
// If repo packages found, install them
operations::install(sorted.repo.clone(), options);
operations::install(&sorted.repo, options);
}
if !sorted.aur.is_empty() {
if !repo && !aur && !sorted.aur.is_empty() || aur && !sorted.aur.is_empty() {
// If AUR packages found, install them
operations::aur_install(sorted.aur.clone(), options);
operations::aur_install(sorted.aur, options, cachedir);
}
// Show optional dependencies for installed packages
info!("Showing optional dependencies for installed packages");
for r in sorted.repo {
info!("{}:", r);
std::process::Command::new("expac")
.args(&["-S", "-l", "\n ", " %O", &r])
.spawn()
.unwrap()
.wait()
.unwrap();
}
for a in sorted.aur {
info!("{}:", a);
let dir_bytes = std::process::Command::new("mktemp")
.arg("-d")
.output()
.unwrap()
.stdout;
let dir = String::from_utf8(dir_bytes).unwrap();
std::process::Command::new("bash")
.arg("-c")
.arg(format!("\
cd {}
curl -L https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h={} -o PKGBUILD -s
source PKGBUILD
printf ' %s\\n' \"${{optdepends[@]}}\"
", dir, a))
.spawn()
.unwrap()
.wait()
.unwrap();
std::fs::remove_dir_all(&std::path::Path::new(&dir.trim())).unwrap();
if packages.len() > 1 && config.base.highlight_optdepends {
info!("Showing optional dependencies for installed packages");
for p in packages {
let out = std::process::Command::new("expac")
.args(&["-Q", "-l", "\n ", " %O", &p])
.output()
.unwrap()
.stdout;
let out = String::from_utf8(out).unwrap().trim().to_string();
if !out.is_empty() {
info!("{}:", p);
println!(" {}", out);
}
}
}
}
@ -133,50 +181,90 @@ fn cmd_remove(args: RemoveArgs, options: Options) {
info!("Uninstalling packages: {}", &packages.join(", "));
// Remove packages
operations::uninstall(packages, options);
operations::uninstall(&packages, options);
}
fn cmd_search(args: SearchArgs, options: Options) {
fn cmd_search(args: &SearchArgs, options: Options) {
// Initialise variables
let query_string = args.search.join(" ");
if args.aur {
info!("Searching AUR for {}", &query_string);
// Search AUR
operations::aur_search(&query_string, options);
}
if args.repo {
info!("Searching repos for {}", &query_string);
// Logic for searching
let repo = args.repo || env::args().collect::<Vec<String>>()[1] == "-Ssr";
let aur = args.aur || env::args().collect::<Vec<String>>()[1] == "-Ssa";
let both = !repo && !aur;
// Start repo spinner
let repo_results = if repo || both {
let rsp = spinner!("Searching repos for {}", query_string);
// Search repos
operations::search(&query_string, options);
}
let ret = operations::search(&query_string, options);
rsp.stop_bold("Repo search complete");
ret
} else {
"".to_string()
};
// Start AUR spinner
let aur_results = if aur || both {
// Strip query of any non-alphanumeric characters
let query_string = query_string.replace(|c: char| !c.is_alphanumeric() && c != '-', "");
let asp = spinner!("Searching AUR for {}", query_string);
// Search AUR
let ret = operations::aur_search(&query_string, options);
asp.stop_bold("AUR search complete");
ret
} else {
"".to_string()
};
if !args.aur && !args.repo {
info!("Searching AUR and repos for {}", &query_string);
let results = repo_results + "\n" + &aur_results;
// If no search type specified, search both
operations::search(&query_string, options);
operations::aur_search(&query_string, options);
// Print results either way, so that the user can see the results after they exit `less`
let text = if internal::uwu_enabled() {
uwu!(results.trim())
} else {
results.trim().to_string()
};
println!("{}", text);
// Check if results are longer than terminal height
if results.lines().count() > crossterm::terminal::size().unwrap().1 as usize {
// If so, paginate results
#[allow(clippy::let_underscore_drop)]
let _ = pager(&results.trim().to_string());
}
}
fn cmd_query(args: QueryArgs) {
if args.aur {
fn cmd_query(args: &QueryArgs) {
let aur = args.aur
|| env::args().collect::<Vec<String>>()[1] == "-Qa"
|| env::args().collect::<Vec<String>>()[1] == "-Qm";
let repo = args.repo
|| env::args().collect::<Vec<String>>()[1] == "-Qr"
|| env::args().collect::<Vec<String>>()[1] == "-Qn";
let both = !aur && !repo;
if aur {
// If AUR query, query AUR
ShellCommand::pacman()
.arg("-Qm")
.wait_success()
.silent_unwrap(AppExitCode::PacmanError);
}
if args.repo {
if repo {
// If repo query, query repos
ShellCommand::pacman()
.arg("-Qn")
.wait_success()
.silent_unwrap(AppExitCode::PacmanError);
}
if !args.repo && !args.aur {
if both {
// If no query type specified, query both
ShellCommand::pacman()
.arg("-Qn")
@ -197,7 +285,25 @@ fn cmd_info(args: InfoArgs) {
.silent_unwrap(AppExitCode::PacmanError);
}
fn cmd_upgrade(args: UpgradeArgs, options: Options) {
fn cmd_upgrade(args: UpgradeArgs, options: Options, cachedir: &str) {
info!("Performing system upgrade");
operations::upgrade(options, args);
operations::upgrade(options, args, cachedir);
}
fn cmd_gencomp(args: &GenCompArgs) {
let shell: Shell = Shell::from_str(&args.shell).unwrap_or_else(|e| {
crash!(AppExitCode::Other, "Invalid shell: {}", e);
});
if shell == Shell::Zsh {
crash!(
AppExitCode::Other,
"Zsh shell completions are currently unsupported due to a bug in the clap_completion crate"
);
};
shell.generate(
&<args::Args as clap::CommandFactory>::command(),
&mut std::io::stderr(),
);
}

@ -1,19 +1,243 @@
use chrono::{Local, TimeZone};
use std::env::set_current_dir;
use std::fs::remove_dir_all;
use std::path::Path;
use std::process::Command;
use std::{env, fs};
use crate::internal::commands::ShellCommand;
use crate::internal::config;
use crate::internal::error::SilentUnwrap;
use crate::internal::exit_code::AppExitCode;
use crate::internal::rpc::rpcinfo;
use crate::internal::sort;
use crate::operations::install;
use crate::{crash, info, log, prompt, warn, Options};
pub fn aur_install(a: Vec<String>, options: Options) {
// Initialise variables
const AUR_CACHE: &str = ".cache/ame";
/// Return a list of all files/dirs in a directory.
fn list(dir: &str) -> Vec<String> {
let dirs = fs::read_dir(Path::new(&dir)).unwrap();
let dirs: Vec<String> = dirs
.map(|dir| {
(*dir
.unwrap()
.path()
.to_str()
.unwrap()
.split('/')
.collect::<Vec<&str>>()
.last()
.unwrap())
.to_string()
})
.collect();
dirs
}
/// Returns and creates a temporary directory for amethyst to use
fn mktemp() -> String {
let tempdir = Command::new("mktemp")
.args(&["-d", "/tmp/ame.XXXXXX.tmp"])
.output()
.unwrap()
.stdout;
String::from_utf8(tempdir).unwrap().trim().to_string()
}
/// Help the user review and/or edit an AUR package before installing
fn review(cachedir: &str, pkg: &str, orig_cachedir: &str) {
// Prompt user to view PKGBUILD
let p0 = prompt!(default false, "Would you like to review and/or edit {}'s PKGBUILD (and any adjacent build files if present)?", pkg);
if p0 {
info!("This will drop you into a standard `bash` shell (unless set otherwise in the config) in the package's cache directory. If any changes are made, you will be prompted whether to save them to your home directory. To stop reviewing/editing, just run `exit`");
let p1 = prompt!(default true,
"Continue?"
);
if p1 {
let config = config::read();
let cdir = env::current_dir().unwrap().to_str().unwrap().to_string();
set_current_dir(Path::new(&format!("{}/{}", &cachedir, pkg))).unwrap();
if config.extra.review_user_shell {
Command::new(&env::var("SHELL").unwrap())
.spawn()
.unwrap()
.wait()
.unwrap();
} else {
ShellCommand::bash().wait().unwrap();
}
set_current_dir(Path::new(&cdir)).unwrap();
// Prompt user to save changes
let p2 = prompt!(default false,
"Save changes to package {}?",
pkg
);
if p2 {
// Save changes to ~/.local/share
let dest = format!(
"{}-saved-{}",
pkg,
chrono::Local::now()
.naive_local()
.format("%Y-%m-%d_%H-%M-%S")
);
Command::new("cp")
.arg("-r")
.arg(format!("{}/{}", cachedir, pkg))
.arg(format!(
"{}/.local/share/ame/{}",
env::var("HOME").unwrap(),
dest
))
.spawn()
.unwrap()
.wait()
.unwrap();
// Alert user
info!("Saved changes to ~/.local/share/ame/{}", dest);
};
}
}
// Prompt user to continue
let p = prompt!(default true, "Would you still like to install {}?", pkg);
if !p {
// If not, crash
if orig_cachedir.is_empty() {
fs::remove_dir_all(format!("{}/{}", cachedir, pkg)).unwrap();
}
crash!(AppExitCode::UserCancellation, "Not proceeding");
};
}
/// Finalize a build/install process
fn finish(cachedir: &str, pkg: &str, options: &Options) {
// Install all packages from cachedir except `pkg` using --asdeps
let dirs = list(cachedir);
// Get a list of packages in cachedir
if dirs.len() > 1 {
info!("Installing AUR dependencies for {}", pkg);
let cmd = std::process::Command::new("bash")
.args(&[
"-cO",
"extglob",
format!(
"sudo pacman -U --asdeps {}/!({})/*.pkg.tar.* {}",
cachedir,
pkg,
if options.noconfirm { "--noconfirm" } else { "" }
)
.as_str(),
])
.spawn()
.unwrap()
.wait()
.unwrap();
if cmd.success() {
info!("All AUR dependencies for package {} installed", pkg);
} else {
crash!(
AppExitCode::PacmanError,
"AUR dependencies failed to install"
);
}
}
// Install package explicitly
info!("Installing {}", pkg);
let cmd = std::process::Command::new("bash")
.args(&[
"-c",
format!(
"sudo pacman -U {}/{}/*.pkg.tar.* {}",
cachedir,
pkg,
if options.noconfirm { "--noconfirm" } else { "" }
)
.as_str(),
])
.spawn()
.unwrap()
.wait()
.unwrap();
if cmd.success() {
info!("{} installed!", pkg);
} else {
crash!(AppExitCode::PacmanError, "{} failed to install", pkg);
}
}
/// Clone a package from the AUR
fn clone(pkg: &String, pkgcache: &str, options: &Options) {
let url = crate::internal::rpc::URL;
let cachedir = format!("{}/.cache/ame/", env::var("HOME").unwrap());
// See if package is already cloned to AUR_CACHE
let dirs = list(pkgcache);
if dirs.contains(pkg) {
// Enter directory and git pull
if options.verbosity > 1 {
log!("Updating cached PKGBUILD for {}", pkg);
}
info!("Updating cached package source");
set_current_dir(Path::new(&format!(
"{}/{}/{}",
env::var("HOME").unwrap(),
AUR_CACHE,
pkg
)))
.unwrap();
ShellCommand::git()
.arg("pull")
.wait()
.silent_unwrap(AppExitCode::GitError);
} else {
// Clone package into cachedir
if options.verbosity >= 1 {
log!("Cloning {} into cachedir", pkg);
}
info!("Cloning package source");
set_current_dir(Path::new(&pkgcache)).unwrap();
ShellCommand::git()
.arg("clone")
.arg(format!("{}/{}", url, pkg))
.wait()
.silent_unwrap(AppExitCode::GitError);
// Enter directory and `makepkg -o` to fetch sources
if options.verbosity > 1 {
log!("Fetching sources for {}", pkg);
}
info!("Fetching sources");
set_current_dir(Path::new(&format!(
"{}/{}/{}",
env::var("HOME").unwrap(),
AUR_CACHE,
pkg
)))
.unwrap();
ShellCommand::makepkg()
.arg("-od")
.wait()
.silent_unwrap(AppExitCode::MakePkgError);
}
}
/// General function to handle installing AUR packages.
pub fn aur_install(a: Vec<String>, options: Options, orig_cachedir: &str) {
// Initialise variables
let cachedir = if options.asdeps || !orig_cachedir.is_empty() {
orig_cachedir.to_string()
} else {
mktemp()
};
let pkgcache = format!("{}/{}", env::var("HOME").unwrap(), AUR_CACHE);
let verbosity = options.verbosity;
let noconfirm = options.noconfirm;
@ -23,12 +247,17 @@ pub fn aur_install(a: Vec<String>, options: Options) {
info!("Installing packages {} from the AUR", a.join(", "));
let mut failed = vec![];
let mut failed: Vec<String> = vec![];
for package in a {
// Query AUR for package info
let rpcres = rpcinfo(package);
// Don't process packages if they are already in the cachedir
let dirs = list(&cachedir);
if dirs.contains(&package) {
continue;
}
// Query AUR for package info
let rpcres = rpcinfo(&package);
if !rpcres.found {
// If package isn't found, break
break;
@ -36,51 +265,60 @@ pub fn aur_install(a: Vec<String>, options: Options) {
// Get package name
let pkg = &rpcres.package.as_ref().unwrap().name;
let ood = rpcres.package.as_ref().unwrap().out_of_date;
if verbosity >= 1 {
log!("Cloning {} into cachedir", pkg);
// If package is out of date, warn user
if ood.is_some() {
warn!(
"Package {} is marked as out of date since [{}], it might be broken, not install or not build properly",
pkg,
Local.timestamp(ood.unwrap().try_into().unwrap(), 0).date_naive()
);
let p = prompt!(default false, "Would you like to continue?");
if !p {
break;
}
}
info!("Cloning package source");
// Clone package into cachedir
set_current_dir(Path::new(&cachedir)).unwrap();
ShellCommand::git()
.arg("clone")
.arg(format!("{}/{}", url, pkg))
.wait()
.silent_unwrap(AppExitCode::GitError);
if verbosity >= 1 {
log!(
"Cloned {} into cachedir, moving on to resolving dependencies",
clone(pkg, &pkgcache, &options);
// Copy package from AUR_CACHE to cachedir
Command::new("cp")
.arg("-r")
.arg(format!(
"{}/{}/{}",
env::var("HOME").unwrap(),
AUR_CACHE,
pkg
);
log!(
"Raw dependencies for package {} are:\n{:?}",
pkg,
rpcres.package.as_ref().unwrap().depends.join(", ")
);
log!(
"Raw makedepends for package {} are:\n{:?}",
pkg,
rpcres.package.as_ref().unwrap().make_depends.join(", ")
);
}
))
.arg(format!("{}/{}", cachedir, pkg))
.spawn()
.unwrap()
.wait()
.unwrap();
// Sort dependencies and makedepends
if verbosity >= 1 {
log!("Sorting dependencies and makedepends");
}
let mut sorted = crate::internal::sort(&rpcres.package.as_ref().unwrap().depends, options);
let mut md_sorted =
crate::internal::sort(&rpcres.package.as_ref().unwrap().make_depends, options);
let mut sorted = sort(&rpcres.package.as_ref().unwrap().depends, options);
let mut md_sorted = sort(&rpcres.package.as_ref().unwrap().make_depends, options);
if verbosity >= 1 {
log!("Sorted dependencies for {} are:\n{:?}", pkg, &sorted);
log!("Sorted makedepends for {} are:\n{:?}", pkg, &md_sorted);
}
// If any dependencies are not found in AUR or repos, crash
if !sorted.nf.is_empty() || !md_sorted.nf.is_empty() {
crash!(
AppExitCode::MissingDeps,
"Could not find dependencies {} for package {}, aborting",
sorted.nf.join(", "),
pkg,
);
}
// Create newopts struct for installing dependencies
let newopts = Options {
verbosity,
@ -98,7 +336,7 @@ pub fn aur_install(a: Vec<String>, options: Options) {
.split_whitespace()
.collect::<Vec<&str>>()
.iter()
.map(|s| s.to_string())
.map(|s| (*s).to_string())
.collect::<Vec<String>>();
// Remove installed packages from sorted dependencies and makedepends
@ -107,100 +345,47 @@ pub fn aur_install(a: Vec<String>, options: Options) {
}
sorted.aur.retain(|x| !installed.contains(x));
sorted.repo.retain(|x| !installed.contains(x));
md_sorted.aur.retain(|x| !installed.contains(x));
md_sorted.repo.retain(|x| !installed.contains(x));
// If dependencies are not found in AUR or repos, crash
if !sorted.nf.is_empty() || !md_sorted.nf.is_empty() {
crash!(
AppExitCode::MissingDeps,
"Could not find dependencies {} for package {}, aborting",
sorted.nf.join(", "),
pkg,
);
}
// Prompt user to review/edit PKGBUILD
if !noconfirm {
// Prompt user to view PKGBUILD
let p1 = prompt!(default false,
"Would you like to review {}'s PKGBUILD (and any .install files if present)?",
pkg
);
let editor: &str = &env::var("PAGER").unwrap_or_else(|_| "less".parse().unwrap());
if p1 {
// Open PKGBUILD in pager
Command::new(editor)
.arg(format!("{}/PKGBUILD", pkg))
.spawn()
.unwrap()
.wait()
.unwrap();
// Check if any .install files are present
let status = ShellCommand::bash()
.arg("-c")
.arg(format!("ls {}/*.install &> /dev/null", pkg))
.wait()
.silent_unwrap(AppExitCode::Other);
if status.success() {
// If so, open them too
ShellCommand::bash()
.arg("-c")
.arg(format!("{} {}/*.install", editor, pkg))
.wait()
.silent_unwrap(AppExitCode::Other);
}
// Prompt user to continue
let p2 = prompt!(default true, "Would you still like to install {}?", pkg);
if !p2 {
// If not, crash
fs::remove_dir_all(format!("{}/{}", cachedir, pkg)).unwrap();
crash!(AppExitCode::UserCancellation, "Not proceeding");
}
}
review(&cachedir, pkg, orig_cachedir);
}
info!("Moving on to install dependencies");
// Install dependencies and makedepends
info!("Moving on to install dependencies");
if !sorted.repo.is_empty() {
crate::operations::install(sorted.repo, newopts);
install(&sorted.repo, newopts);
}
if !sorted.aur.is_empty() {
crate::operations::aur_install(sorted.aur, newopts);
aur_install(sorted.aur, newopts, &cachedir.clone());
}
if !md_sorted.repo.is_empty() {
crate::operations::install(md_sorted.repo, newopts);
install(&md_sorted.repo, newopts);
}
if !md_sorted.aur.is_empty() {
crate::operations::aur_install(md_sorted.aur, newopts);
aur_install(md_sorted.aur, newopts, &cachedir.clone());
}
// Build makepkg args
let mut makepkg_args = vec!["-rsci", "--skippgp", "--needed"];
let mut makepkg_args = vec!["-rcd", "--skippgp", "--needed"];
if options.asdeps {
makepkg_args.push("--asdeps")
makepkg_args.push("--asdeps");
}
if options.noconfirm {
makepkg_args.push("--noconfirm")
makepkg_args.push("--noconfirm");
}
info!("Building time!");
// Enter cachedir and build package
info!("Building time!");
set_current_dir(format!("{}/{}", cachedir, pkg)).unwrap();
let status = ShellCommand::makepkg()
.args(makepkg_args)
.wait()
.silent_unwrap(AppExitCode::MakePkgError);
if !status.success() {
// If build failed, push to failed vec
fs::remove_dir_all(format!("{}/{}", cachedir, pkg)).unwrap();
failed.push(pkg.clone());
return;
}
@ -208,12 +393,35 @@ pub fn aur_install(a: Vec<String>, options: Options) {
// Return to cachedir
set_current_dir(&cachedir).unwrap();
// Remove package from cache
remove_dir_all(format!("{}/{}", cachedir, &pkg)).unwrap();
// Finish installation process
if !options.asdeps {
finish(&cachedir, pkg, &options);
}
}
// If any packages failed to build, warn user with failed packages
if !failed.is_empty() {
warn!("Failed to build packages {}", failed.join(", "));
let failed_str = format!("{}.failed", cachedir);
warn!(
"Failed to build packages {}, keeping cache directory at {} for manual inspection",
failed.join(", "),
if orig_cachedir.is_empty() {
&cachedir
} else {
&failed_str
}
);
if orig_cachedir.is_empty() {
Command::new("mv")
.args(&[&cachedir, &format!("{}.failed", cachedir)])
.spawn()
.unwrap()
.wait()
.unwrap();
}
} else if !options.asdeps && orig_cachedir.is_empty() {
rm_rf::remove(&cachedir).unwrap_or_else(|e|
crash!(AppExitCode::Other, "Could not remove cache directory at {}: {}. This could be a permissions issue with fakeroot, try running `sudo rm -rf {}`", cachedir, e, cachedir)
);
}
}

@ -9,6 +9,7 @@ use crate::log;
use crate::prompt;
use crate::Options;
/// Help the user in clearing orphaned packages and pacman cache.
pub fn clean(options: Options) {
let verbosity = options.verbosity;
let noconfirm = options.noconfirm;
@ -69,14 +70,27 @@ pub fn clean(options: Options) {
}
}
// Prompt the user whether to clear the Amethyst cache
let clear_ame_cache = prompt!(default false, "Clear Amethyst's internal PKGBUILD cache?");
if clear_ame_cache {
// Remove ~/.cache/ame
Command::new("rm")
.arg("-rf")
.arg("~/.cache/ame")
.spawn()
.unwrap()
.wait()
.unwrap();
}
// Prompt the user whether to clear cache or not
let clear_cache = if !noconfirm {
prompt!(default false, "Also clear pacman's package cache?")
} else {
let clear_pacman_cache = if noconfirm {
true
} else {
prompt!(default false, "Also clear pacman's package cache?")
};
if clear_cache {
if clear_pacman_cache {
// Build pacman args
let mut pacman_args = vec!["-Sc"];
if noconfirm {

@ -3,7 +3,8 @@ use crate::internal::error::SilentUnwrap;
use crate::internal::exit_code::AppExitCode;
use crate::{crash, info, log, Options};
pub fn install(packages: Vec<String>, options: Options) {
/// Help the user install a package from the pacman repos
pub fn install(packages: &[String], options: Options) {
info!("Installing packages {} from repos", &packages.join(", "));
// Build pacman args
@ -25,7 +26,7 @@ pub fn install(packages: Vec<String>, options: Options) {
let status = ShellCommand::pacman()
.elevated()
.args(opers)
.args(&packages)
.args(packages)
.wait()
.silent_unwrap(AppExitCode::PacmanError);
if !status.success() {

@ -1,53 +1,146 @@
use chrono::{Local, TimeZone};
use colored::Colorize;
use textwrap::wrap;
use crate::internal::commands::ShellCommand;
use crate::internal::error::SilentUnwrap;
use crate::internal::exit_code::AppExitCode;
use crate::internal::rpc::rpcsearch;
use crate::{log, Options};
pub fn aur_search(query: &str, options: Options) {
// Initialise variables
let verbosity = options.verbosity;
#[allow(clippy::module_name_repetitions)]
/// Searches for packages from the AUR and returns wrapped results
pub fn aur_search(query: &str, options: Options) -> String {
// Query AUR for package info
let res = rpcsearch(query.to_string());
let res = rpcsearch(query);
// Get verbosity
let verbosity = options.verbosity;
// Format output
let mut results_vec = vec![];
for package in &res.results {
println!(
"aur/{} {}\n {}",
package.name,
package.version,
package
.description
.as_ref()
.unwrap_or(&"No description".to_string())
)
// Define wrapping options
let opts = textwrap::Options::new(crossterm::terminal::size().unwrap().0 as usize - 4)
.subsequent_indent(" ");
let result = format!(
"{}{} {} {}\n {}",
"aur/".cyan().bold(),
package.name.bold(),
package.version.green().bold(),
if package.out_of_date.is_some() {
format!(
"[out of date: since {}]",
Local
.timestamp(package.out_of_date.unwrap().try_into().unwrap(), 0)
.date_naive()
)
.red()
.bold()
} else {
"".bold()
},
wrap(
package
.description
.as_ref()
.unwrap_or(&"No description".to_string()),
opts,
)
.join("\n"),
);
results_vec.push(result);
}
if verbosity >= 1 {
log!("Found {} resuls for \"{}\" in AUR", res.resultcount, query);
if verbosity > 1 {
log!(
"Found {} results for \"{}\" in the AUR",
res.results.len(),
query
);
}
results_vec.join("\n")
}
pub fn repo_search(query: &str, options: Options) {
struct SearchResult {
repo: String,
name: String,
version: String,
description: String,
}
#[allow(clippy::module_name_repetitions)]
/// Searches for packages from the repos and returns wrapped results
pub fn repo_search(query: &str, options: Options) -> String {
// Initialise variables
let verbosity = options.verbosity;
// Query pacman for package info
let output = ShellCommand::pacman()
.arg("-Ss")
let output = ShellCommand::bash()
.args(&["-c", &format!("expac -Ss '%r\\\\%n\\\\%v\\\\%d' {}", query)])
.arg(query)
.wait_with_output()
.silent_unwrap(AppExitCode::PacmanError)
.stdout;
// Split output into lines
let lines = output.trim().split('\n');
// Initialise results vector
let mut results_vec: Vec<SearchResult> = vec![];
let clone = lines.clone().collect::<Vec<&str>>();
if clone.len() == 1 && clone[0].is_empty() {
// If no results, return empty string
return "".to_string();
}
// Iterate over lines
for line in lines {
let parts: Vec<&str> = line.split('\\').collect();
let res = SearchResult {
repo: parts[0].to_string(),
name: parts[1].to_string(),
version: parts[2].to_string(),
description: parts[3].to_string(),
};
results_vec.push(res);
}
if verbosity >= 1 {
log!(
"Found {} results for \"{}\" in repos",
&output.split('\n').count() / 2,
&results_vec.len(),
&query
);
}
println!("{}", output)
// Format output
let results_vec = results_vec
.into_iter()
.map(|res| {
let opts = textwrap::Options::new(crossterm::terminal::size().unwrap().0 as usize - 4)
.subsequent_indent(" ");
format!(
"{}{}{} {}\n {}",
res.repo.purple().bold(),
"/".purple().bold(),
res.name.bold(),
res.version.green().bold(),
if res.description.is_empty() {
"No description".to_string()
} else {
wrap(&res.description, opts).join("\n")
},
)
})
.collect::<Vec<String>>();
if output.trim().is_empty() {
"".to_string()
} else {
results_vec.join("\n")
}
}

@ -1,15 +1,13 @@
use std::path::Path;
use std::{env, fs};
use crate::internal::commands::ShellCommand;
use crate::internal::error::SilentUnwrap;
use crate::internal::exit_code::AppExitCode;
use crate::{log, Options};
pub fn uninstall(packages: Vec<String>, options: Options) {
/// Helps the user in uninstalling installed packages.
pub fn uninstall(packages: &[String], options: Options) {
// Build pacman args
let mut pacman_args = vec!["-Rs"];
pacman_args.append(&mut packages.iter().map(|s| s.as_str()).collect());
pacman_args.append(&mut packages.iter().map(String::as_str).collect());
if options.noconfirm {
pacman_args.push("--noconfirm");
}
@ -28,25 +26,4 @@ pub fn uninstall(packages: Vec<String>, options: Options) {
if verbosity >= 1 {
log!("Uninstalling packages: {:?} exited with code 0", &packages);
}
for package in packages {
// Remove old cache directory
if Path::new(&format!(
"{}/.cache/ame/{}",
env::var("HOME").unwrap(),
package
))
.exists()
{
if verbosity >= 1 {
log!("Old cache directory found, deleting");
}
fs::remove_dir_all(Path::new(&format!(
"{}/.cache/ame/{}",
env::var("HOME").unwrap(),
package
)))
.unwrap();
}
}
}

@ -13,7 +13,8 @@ struct QueriedPackage {
pub version: String,
}
pub fn upgrade(options: Options, args: UpgradeArgs) {
/// Helps the user upgrade installed packages, repo and AUR.
pub fn upgrade(options: Options, args: UpgradeArgs, cachedir: &str) {
// Initialise variables
let verbosity = options.verbosity;
let noconfirm = options.noconfirm;
@ -55,7 +56,7 @@ pub fn upgrade(options: Options, args: UpgradeArgs) {
}
if args.repo && args.aur {
let cont = prompt!(default false, "Continue to upgrade AUR packages?");
let cont = prompt!(default true, "Continue to upgrade AUR packages?");
if !cont {
// If user doesn't want to continue, break
info!("Exiting");
@ -106,15 +107,26 @@ pub fn upgrade(options: Options, args: UpgradeArgs) {
let mut aur_upgrades = vec![];
for pkg in parsed_non_native {
// Query AUR
let rpc_result = rpcinfo((&*pkg.name).to_string());
let rpc_result = rpcinfo(&pkg.name);
if !rpc_result.found {
// If package not found, skip
continue;
}
// Run `vercmp` to compare versions
let vercmp_result = std::process::Command::new("vercmp")
.arg(&pkg.version)
.arg(&rpc_result.package.unwrap().version)
.output()
.unwrap();
let vercmp_result = String::from_utf8(vercmp_result.stdout).unwrap();
if verbosity >= 1 {
log!("Vercmp returned {:?}", vercmp_result);
}
// If versions differ, push to a vector
if rpc_result.package.unwrap().version != pkg.version {
if vercmp_result.trim() == "-1" {
aur_upgrades.push(pkg.name);
}
}
@ -122,16 +134,16 @@ pub fn upgrade(options: Options, args: UpgradeArgs) {
sp.stop_bold("Finished!");
// If vector isn't empty, prompt to install AUR packages from vector, effectively upgrading
if !aur_upgrades.is_empty() {
if aur_upgrades.is_empty() {
info!("No upgrades available for installed AUR packages");
} else {
let cont = prompt!(default true,
"Found AUR packages {} have new versions available, upgrade?",
"AUR packages {} have new versions available, upgrade?",
aur_upgrades.join(", "),
);
if cont {
aur_install(aur_upgrades, options);
aur_install(aur_upgrades, options, cachedir);
};
} else {
info!("No upgrades available for installed AUR packages");
}
}

Loading…
Cancel
Save