diff --git a/.gitignore b/.gitignore index 0c99807..502c7fe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ target/ Cargo.lock ame +ame.exe .vscode -aur_pkgs.db -test.sql +.idea diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..161f3a0 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,28 @@ +# Crystal Linux Contributing Guidelines + +#### !! Always make sure to `git pull` before doing any work to avoid commit hell !! + +### Pre-Commit Checks + +- Make sure to `cargo fmt` your code before every commit push +- Unless in specific edge cases, don't push code that doesn't pass `cargo check` +- Try to correct any code with `cargo clippy` before you push + +### Formatting + +- UNIX line endings (LF instead of CRLF) +- 4 spaces per TAB + +### Good Practices + +- Try to use .unwrap() as little as possible +- Try to never use panic!() in production code, always try to have a possible way to resolve errors, even if it's just + unwrap_or/_else() +- Never use println!() or eprintln!() in finalised code. Using string functions (e.g. info() in Amethyst v3.0.0) is + preferred +- Compartmentalise as much as you can, avoid writing the exact same line of code 50 times if you can turn it into a + function + +### Examples of these guidelines in practice + +- https://git.getcryst.al/crystal/ame/src/branch/rewrite \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index cbbfd3d..f68234a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,19 +1,60 @@ [package] +name = "Amethyst" +version = "3.0.0" +authors = ["michal ", "axtlos "] +edition = "2021" +description = "A fast and efficient AUR helper." +license-file = "LICENSE.md" + +[features] +pkg-warner = [] + +[[bin]] +name = "apt" +path = "src/bin/apt.rs" +required-features = ["pkg-warner"] + +[[bin]] +name = "apt-get" +path = "src/bin/apt-get.rs" +required-features = ["pkg-warner"] + +[[bin]] +name = "dnf" +path = "src/bin/dnf.rs" +required-features = ["pkg-warner"] + +[[bin]] +name = "eopkg" +path = "src/bin/eopkg.rs" +required-features = ["pkg-warner"] + +[[bin]] +name = "yum" +path = "src/bin/yum.rs" +required-features = ["pkg-warner"] + +[[bin]] +name = "zypper" +path = "src/bin/zypper.rs" +required-features = ["pkg-warner"] + +[[bin]] name = "ame" -version = "0.0.0" -authors = [ "jnats ", "axtlos " ] -edition = "2018" -description = "a fast and efficient aur helper." +path = "src/main.rs" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[profile.release] +incremental = true +debug = false +lto = "fat" +codegen-units = 1 [dependencies] +mimalloc = { version = "0.1.27", default-features = false } +clap = { version = "2.34.0", default-features = false, features = ["suggestions"] } +regex = { version = "1.5.4", default-features = false, features = ["std", "unicode-perl"] } runas = "0.2.1" -ansi_term = "0.12.1" -uwuizer = "0.2.1" -moins = "0.5.0" -regex = { version = "1.5.4", default-features = false } -toml = "0.5.8" -sqlite = "0.26.0" -reqwest = { version = "0.11.7", default-features = false, features = [ "blocking", "json", "default-tls" ] } -serde = { version = "1.0.90", default-features = false, features = [ "derive" ] } \ No newline at end of file +rusqlite = { version = "0.26.3", default-features = false } +ureq = { version = "2.4.0", default-features = false, features = ["native-tls", "json"] } +serde = { version = "1.0.90", default-features = false, features = ["derive", "serde_derive"] } +native-tls = "0.2.8" \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..ba4c143 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,23 @@ +The Nolicense Revision 2.1 - Monday 15th November 2021 + +Copyright (c) 2022 Crystal Linux Team + +Everyone is permitted to freely copy and distribute this license document. Modified redistributions are subject to the +following license agreement. + +The Nolicense terms and conditions for copying, distribution and modification of software and any created assets are as +follows: + +- Any unmodified redistributions in either source code or binary form must retain this copyright notice in its entirety. + +- Any and all redistributions or derivative works, whether modified or unmodified, must credit the original author(s) of + the source code, and provide an easily accessible way to find the original author's source code, wherever it may be + published. + +- Derivative works and modified redistributions cannot be published under the same name as the original software, nor + can it be presented as an extension of or newer revision of said software, and unless explicitly permitted, the + original authors name(s) shall not be used to endorse said derivative works. + +This software is provided as-is; Neither the copyright holders nor any contributors to the software are to be held +liable for any damages caused by any files attached. By modifying or redistributing the software in any way, you +automatically agree to the terms of the license agreement. diff --git a/Makefile b/Makefile deleted file mode 100644 index c8c0e52..0000000 --- a/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -debug: - cargo build - ln -sf target/debug/ame . -release: - cargo build --release - ln -sf target/release/ame . -clean: - rm -rf target/ Cargo.lock ame -install: - cargo build --release - sudo cp target/release/ame /usr/bin/ame diff --git a/README.md b/README.md index ae91791..7c005af 100644 --- a/README.md +++ b/README.md @@ -3,49 +3,66 @@ Logo

-

+

Amethyst

-

+

-Discord

+ Discord +

+ +

+Amethyst is a fast, efficient and lightweight AUR helper and Pacman wrapper.
+Made for Crystal, compatible with any Arch-based Linux distribution. +

+ +### Basic usage -

Amethyst is a fast, efficient and lightweight AUR helper and Pacman wrapper. -Made for Crystal, compatible with any Arch-based Linux distribution.

+| 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 | -![](screenshot.png) +### Exit codes overview -## Basic usage -| Action | FreeBSD pkg-style alias | Pacman-style flag(s) | -| ------ | ------ | ------ | -| Install a package | ame ins | ame -S | -| Remove a package | ame rm | ame -R | -| Remove a package with its dependencies | ame purge | ame -Rs | -| Update repository | ame upd | ame -Sy | -| Upgrade a package | ame upg | ame -Syu | -| Search for a package in general | ame sea | ame -Ss | -| Search for a package in the official arch repos | ame repsea | ame -Sr | -| Search for a package in aur | ame aursea | ame -Sa | +| 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 | -You can also use any pacman flag! +### How to build: -## How to build: -(Install cargo) +Tested on latest Cargo (1.60.0-nightly) -For release: - - `make clean release` - -For general debug/test: - - `make debug` +
+ +#### Debug/development builds + +- `cargo build` + +#### Optimised/release builds + +- `cargo build --release` -Clean all build directories: - - `make clean` +#### Pkg-warner included + +- `cargo build (--release) --all --features=pkg-warner`

-```sh -echo "AME_UWU=YES" >> ~/.zshrc # for zsh -echo "AME_UWU=YES" >> ~/.bashrc # for bash -set -Ux AME_UWU YES # for fish -``` -self explanatory + \ No newline at end of file diff --git a/src/bin/apt-get.rs b/src/bin/apt-get.rs new file mode 100644 index 0000000..db60a1d --- /dev/null +++ b/src/bin/apt-get.rs @@ -0,0 +1,14 @@ +use std::env; + +fn main() { + let arg = &env::args().collect::>()[0]; + + println!( + "Sorry for the bother, we don't use \x1b[2;22;35m{}\x1b[0m on Crystal, we use \x1b[2;22;35mame\x1b[0m! Please use that instead!", + arg.split('/') + .collect::>() + .last() + .unwrap() + ); + std::process::exit(0); +} diff --git a/src/bin/apt.rs b/src/bin/apt.rs new file mode 100644 index 0000000..db60a1d --- /dev/null +++ b/src/bin/apt.rs @@ -0,0 +1,14 @@ +use std::env; + +fn main() { + let arg = &env::args().collect::>()[0]; + + println!( + "Sorry for the bother, we don't use \x1b[2;22;35m{}\x1b[0m on Crystal, we use \x1b[2;22;35mame\x1b[0m! Please use that instead!", + arg.split('/') + .collect::>() + .last() + .unwrap() + ); + std::process::exit(0); +} diff --git a/src/bin/dnf.rs b/src/bin/dnf.rs new file mode 100644 index 0000000..db60a1d --- /dev/null +++ b/src/bin/dnf.rs @@ -0,0 +1,14 @@ +use std::env; + +fn main() { + let arg = &env::args().collect::>()[0]; + + println!( + "Sorry for the bother, we don't use \x1b[2;22;35m{}\x1b[0m on Crystal, we use \x1b[2;22;35mame\x1b[0m! Please use that instead!", + arg.split('/') + .collect::>() + .last() + .unwrap() + ); + std::process::exit(0); +} diff --git a/src/bin/eopkg.rs b/src/bin/eopkg.rs new file mode 100644 index 0000000..db60a1d --- /dev/null +++ b/src/bin/eopkg.rs @@ -0,0 +1,14 @@ +use std::env; + +fn main() { + let arg = &env::args().collect::>()[0]; + + println!( + "Sorry for the bother, we don't use \x1b[2;22;35m{}\x1b[0m on Crystal, we use \x1b[2;22;35mame\x1b[0m! Please use that instead!", + arg.split('/') + .collect::>() + .last() + .unwrap() + ); + std::process::exit(0); +} diff --git a/src/bin/yum.rs b/src/bin/yum.rs new file mode 100644 index 0000000..db60a1d --- /dev/null +++ b/src/bin/yum.rs @@ -0,0 +1,14 @@ +use std::env; + +fn main() { + let arg = &env::args().collect::>()[0]; + + println!( + "Sorry for the bother, we don't use \x1b[2;22;35m{}\x1b[0m on Crystal, we use \x1b[2;22;35mame\x1b[0m! Please use that instead!", + arg.split('/') + .collect::>() + .last() + .unwrap() + ); + std::process::exit(0); +} diff --git a/src/bin/zypper.rs b/src/bin/zypper.rs new file mode 100644 index 0000000..db60a1d --- /dev/null +++ b/src/bin/zypper.rs @@ -0,0 +1,14 @@ +use std::env; + +fn main() { + let arg = &env::args().collect::>()[0]; + + println!( + "Sorry for the bother, we don't use \x1b[2;22;35m{}\x1b[0m on Crystal, we use \x1b[2;22;35mame\x1b[0m! Please use that instead!", + arg.split('/') + .collect::>() + .last() + .unwrap() + ); + std::process::exit(0); +} diff --git a/src/database/add.rs b/src/database/add.rs new file mode 100644 index 0000000..eaea9d6 --- /dev/null +++ b/src/database/add.rs @@ -0,0 +1,26 @@ +use std::env; +use std::path::Path; + +use rusqlite::Connection; + +use crate::{crash, log, Options}; +use crate::internal::rpc::Package; + +pub fn add(pkg: Package, options: Options) { + let conn = Connection::open(Path::new(&format!( + "{}/.local/share/ame/db.sqlite", + env::var("HOME").unwrap() + ))) + .expect("Couldn't connect to database"); + + if options.verbosity >= 1 { + log(format!("Adding package {} to database", pkg.name)); + } + + conn.execute("INSERT OR REPLACE INTO packages (name, version, description, depends, make_depends) VALUES (?1, ?2, ?3, ?4, ?5)", + [&pkg.name, &pkg.version, &pkg.description.unwrap_or_else(|| "No description found.".parse().unwrap()), &pkg.depends.join(" "), &pkg.make_depends.join(" ")], + ).unwrap_or_else(|e| { + crash(format!("Failed adding package {} to the database: {}", pkg.name, e), 2); + 1 + }); +} diff --git a/src/database/initialise.rs b/src/database/initialise.rs new file mode 100644 index 0000000..d7121a6 --- /dev/null +++ b/src/database/initialise.rs @@ -0,0 +1,38 @@ +use std::env; +use std::path::Path; + +use rusqlite::Connection; + +use crate::{crash, log, Options}; + +pub fn init(options: Options) { + let path = format!("{}/.local/share/ame/db.sqlite", env::var("HOME").unwrap()); + let dbpath = Path::new(&path); + let verbosity = options.verbosity; + + if verbosity >= 1 { + log(format!("Creating database at {}", &path)); + } + + let conn = + Connection::open(dbpath).expect("Couldn't create database at ~/.local/share/ame/db.sqlite"); + + if verbosity >= 1 { + log("Populating database with table".to_string()); + } + + conn.execute( + "CREATE TABLE packages ( + name TEXT PRIMARY KEY NOT NULL, + version TEXT NOT NULL, + description TEXT, + depends BLOB, + make_depends BLOB + )", + [], + ) + .unwrap_or_else(|e| { + crash(format!("Couldn't initialise database: {}", e), 3); + 1 + }); +} \ No newline at end of file diff --git a/src/database/mod.rs b/src/database/mod.rs new file mode 100644 index 0000000..0e79ed6 --- /dev/null +++ b/src/database/mod.rs @@ -0,0 +1,23 @@ +use crate::internal::rpc::Package; +use crate::Options; + +mod add; +mod initialise; +mod query; +mod remove; + +pub fn add(a: Package, options: Options) { + add::add(a, options); +} + +pub fn remove(a: &str, options: Options) { + remove::remove(a, options); +} + +pub fn init(options: Options) { + initialise::init(options); +} + +pub fn query(options: Options) -> Vec { + query::query(options) +} diff --git a/src/database/query.rs b/src/database/query.rs new file mode 100644 index 0000000..6626fa7 --- /dev/null +++ b/src/database/query.rs @@ -0,0 +1,64 @@ +use std::env; +use std::path::Path; + +use rusqlite::Connection; + +use crate::{log, Options}; +use crate::internal::rpc::Package; + +pub fn query(options: Options) -> Vec { + let verbosity = options.verbosity; + + if verbosity >= 1 { + log("Connecting to database".to_string()); + } + + let conn = Connection::open(Path::new(&format!( + "{}/.local/share/ame/db.sqlite", + env::var("HOME").unwrap() + ))) + .expect("Couldn't connect to database"); + + if verbosity >= 1 { + log("Querying database for input".to_string()); + } + + let mut rs = conn.prepare("SELECT * FROM packages;").unwrap(); + let packages_iter = rs + .query_map([], |row| { + Ok(Package { + name: row.get(0).unwrap(), + version: row.get(1).unwrap(), + description: row.get(2).unwrap(), + depends: row + .get::(3) + .unwrap() + .split(' ') + .map(|s| s.to_string()) + .collect::>(), + make_depends: row + .get::(4) + .unwrap() + .split(' ') + .map(|s| s.to_string()) + .collect::>(), + }) + }) + .expect("Couldn't query database for packages"); + + if verbosity >= 1 { + log("Retrieved results".to_string()); + } + + let mut results: Vec = vec![]; + + for package in packages_iter { + results.push(package.unwrap()); + } + + if verbosity >= 1 { + log("Collected results".to_string()); + } + + results +} diff --git a/src/database/remove.rs b/src/database/remove.rs new file mode 100644 index 0000000..c213e3b --- /dev/null +++ b/src/database/remove.rs @@ -0,0 +1,30 @@ +use std::env; +use std::path::Path; + +use rusqlite::Connection; + +use crate::{log, Options}; + +pub fn remove(pkg: &str, options: Options) { + let conn = Connection::open(Path::new(&format!( + "{}/.local/share/ame/db.sqlite", + env::var("HOME").unwrap() + ))) + .expect("Couldn't connect to database"); + + let verbosity = options.verbosity; + + if verbosity >= 1 { + log(format!("Removing package {} from database", pkg)); + } + + conn.execute( + "DELETE FROM packages + WHERE EXISTS + (SELECT * + FROM packages + WHERE name = ?);", + [pkg], + ) + .expect("Couldn't delete package from database"); +} diff --git a/src/internal/clean.rs b/src/internal/clean.rs new file mode 100644 index 0000000..f6fbcc5 --- /dev/null +++ b/src/internal/clean.rs @@ -0,0 +1,25 @@ +use regex::Regex; + +use crate::internal::strings::log; +use crate::Options; + +pub fn clean(a: &[String], options: Options) -> Vec { + let r = Regex::new(r"(\S+)((?:>=|<=|>|<)\S+$)").unwrap(); + let mut cleaned: Vec = vec![]; + let verbosity = options.verbosity; + + for b in a { + if r.captures_iter(b).count() > 0 { + let c = r.captures(b).unwrap().get(1).map_or("", |m| m.as_str()); + cleaned.push(c.to_string()); + } else { + cleaned.push(b.to_string()); + } + } + + if verbosity >= 1 { + log(format!("Cleaned: {:?}\nInto: {:?}", a, cleaned)); + } + + cleaned +} diff --git a/src/internal/initialise.rs b/src/internal/initialise.rs new file mode 100644 index 0000000..d48eec6 --- /dev/null +++ b/src/internal/initialise.rs @@ -0,0 +1,48 @@ +use std::env; +use std::path::Path; + +use crate::internal::strings::{crash, log}; +use crate::Options; + +pub fn init(options: Options) { + let verbosity = options.verbosity; + let homedir = env::var("HOME").unwrap(); + + if !Path::new(&format!("{}/.local/share/ame", homedir)).exists() { + let r = std::fs::create_dir_all(format!("{}/.local/share/ame", homedir)); + match r { + Ok(_) => { + if verbosity >= 1 { + log(format!("Created path: {}/.local/share/ame", homedir)); + } + } + Err(e) => { + crash( + format!("Couldn't create path: {}/.local/share/ame: {}", homedir, e), + 4, + ); + } + } + } + + if !Path::new(&format!("{}/.local/share/ame/db.sqlite", homedir)).exists() { + crate::database::init(options); + } + + if !Path::new(&format!("{}/.cache/ame/", homedir)).exists() { + let r = std::fs::create_dir_all(format!("{}/.cache/ame", homedir)); + match r { + Ok(_) => { + if verbosity >= 1 { + log(format!("Created path: {}/.cache/ame", homedir)); + } + } + Err(e) => { + crash( + format!("Couldn't create path: {}/.cache/ame: {}", homedir, e), + 4, + ); + } + } + } +} diff --git a/src/internal/mod.rs b/src/internal/mod.rs new file mode 100644 index 0000000..06c7006 --- /dev/null +++ b/src/internal/mod.rs @@ -0,0 +1,53 @@ +use crate::Options; + +mod clean; +mod initialise; +pub mod rpc; +mod sort; +mod strings; +pub mod structs; + +pub fn sort(a: &[String], options: Options) -> structs::Sorted { + sort::sort(a, options) +} + +pub fn clean(a: &[String], options: Options) -> Vec { + clean::clean(a, options) +} + +pub fn init(options: Options) { + initialise::init(options); +} + +pub fn info(a: String) { + strings::info(a); +} + +pub fn crash(a: String, b: i32) { + strings::crash(a, b); +} + +pub fn log(a: String) { + strings::log(a); +} + +pub fn prompt(a: String, b: bool) -> bool { + strings::prompt(a, b) +} + +#[macro_export] +macro_rules! uwu { + ($x:expr) => {{ + let uwu: String = String::from_str($x).unwrap(); + + let uwu = uwu.replace("l", "w"); + let uwu = uwu.replace("L", "W"); + let uwu = uwu.replace("r", "w"); + let uwu = uwu.replace("R", "W"); + let uwu = uwu.replace("na", "nya"); + let uwu = uwu.replace("Na", "Nya"); + let uwu = uwu.replace("NA", "NYA"); + + uwu + }}; +} diff --git a/src/internal/rpc.rs b/src/internal/rpc.rs new file mode 100644 index 0000000..341d32a --- /dev/null +++ b/src/internal/rpc.rs @@ -0,0 +1,75 @@ +use std::sync::Arc; + +#[derive(serde::Deserialize, Debug, Clone)] +pub struct Package { + #[serde(rename = "Name")] + pub name: String, + #[serde(rename = "Version")] + pub version: String, + #[serde(rename = "Description")] + pub description: Option, + #[serde(rename = "Depends")] + #[serde(default)] + pub depends: Vec, + #[serde(rename = "MakeDepends")] + #[serde(default)] + pub make_depends: Vec, +} + +#[derive(serde::Deserialize)] +pub struct SearchResults { + pub resultcount: u32, + pub results: Vec, +} + +#[derive(Clone)] +pub struct InfoResults { + pub found: bool, + pub package: Option, +} + +pub const URL: &str = "https://aur.archlinux.org/"; + +pub fn rpcinfo(pkg: String) -> InfoResults { + let tls_connector = Arc::new(native_tls::TlsConnector::new().unwrap()); + let agent = ureq::AgentBuilder::new() + .tls_connector(tls_connector) + .build(); + let res: SearchResults = agent + .get(&format!( + "https://aur.archlinux.org/rpc/?v=5&type=info&arg={}", + pkg + )) + .call() + .unwrap() + .into_json() + .unwrap(); + + if res.results.is_empty() { + InfoResults { + found: false, + package: None, + } + } else { + InfoResults { + found: true, + package: Some(res.results[0].clone()), + } + } +} + +pub fn rpcsearch(pkg: String) -> SearchResults { + let tls_connector = Arc::new(native_tls::TlsConnector::new().unwrap()); + let agent = ureq::AgentBuilder::new() + .tls_connector(tls_connector) + .build(); + agent + .get(&format!( + "https://aur.archlinux.org/rpc/?v=5&type=search&arg={}", + pkg + )) + .call() + .unwrap() + .into_json::() + .unwrap() +} diff --git a/src/internal/sort.rs b/src/internal/sort.rs new file mode 100644 index 0000000..56c3cac --- /dev/null +++ b/src/internal/sort.rs @@ -0,0 +1,46 @@ +use std::process::{Command, Stdio}; + +use crate::internal::{clean, rpc, structs}; +use crate::internal::strings::log; +use crate::Options; + +pub fn sort(input: &[String], options: Options) -> structs::Sorted { + let mut repo: Vec = vec![]; + let mut aur: Vec = vec![]; + let mut nf: Vec = vec![]; + let verbosity = options.verbosity; + + let a = clean(input, options); + + if verbosity >= 1 { + log(format!("Sorting: {:?}", a.join(" "))); + } + + for b in a { + let rs = Command::new("pacman") + .arg("-Ss") + .arg(format!("^{}$", &b)) + .stdout(Stdio::null()) + .status() + .expect("Something has gone wrong"); + + if rpc::rpcinfo(b.to_string()).found { + if verbosity >= 1 { + log(format!("{} found in AUR", b)); + } + aur.push(b.to_string()); + } else if let Some(0) = rs.code() { + if verbosity >= 1 { + log(format!("{} found in repos", b)); + } + repo.push(b.to_string()); + } else { + if verbosity >= 1 { + log(format!("{} not found", b)); + } + nf.push(b.to_string()); + } + } + + structs::Sorted::new(repo, aur, nf) +} diff --git a/src/internal/strings.rs b/src/internal/strings.rs new file mode 100644 index 0000000..1883226 --- /dev/null +++ b/src/internal/strings.rs @@ -0,0 +1,76 @@ +use std::{env, io}; +use std::io::Write; +use std::process::exit; +use std::str::FromStr; +use std::time::UNIX_EPOCH; + +use crate::uwu; + +pub fn info(a: String) { + let a = if env::var("AME_UWU").unwrap_or_else(|_| "".to_string()) == "true" { + uwu!(&a) + } else { + a + }; + + println!("\x1b[2;22;35m❖\x1b[0m \x1b[1;37m{}\x1b[0m", a) +} + +pub fn crash(a: String, b: i32) { + let a = if env::var("AME_UWU").unwrap_or_else(|_| "".to_string()) == "true" { + uwu!(&a) + } else { + a + }; + + println!("\x1b[2;22;31m❌:\x1b[0m \x1b[1;91m{}\x1b[0m", a); + exit(b); +} + +pub fn log(a: String) { + let a = if env::var("AME_UWU").unwrap_or_else(|_| "".to_string()) == "true" + && env::var("AME_UWU_DEBUG").unwrap_or_else(|_| "".to_string()) == "true" + { + uwu!(&a) + } else { + a + }; + + eprintln!( + "{} {}", + std::time::SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(), + a + ); +} + +pub fn prompt(a: String, b: bool) -> bool { + let default = ["[Y/n]", "[y/N]"]; + let i = if b { 0 } else { 1 }; + + let a = if env::var("AME_UWU").unwrap_or_else(|_| "".to_string()) == "true" { + uwu!(&a) + } else { + a + }; + + print!( + "\x1b[2;22;35m?\x1b[0m \x1b[1;37m{}\x1b[0m \x1b[2;22;37m{}\x1b[0m: ", + a, default[i] + ); + + let mut yn: String = String::new(); + + io::stdout().flush().ok(); + let _ = std::io::stdin().read_line(&mut yn); + + if yn.trim().to_lowercase() == "n" || yn.trim().to_lowercase() == "no" { + false + } else if yn.trim().to_lowercase() == "y" || yn.trim().to_lowercase() == "yes" { + true + } else { + b + } +} diff --git a/src/internal/structs.rs b/src/internal/structs.rs new file mode 100644 index 0000000..7fd8007 --- /dev/null +++ b/src/internal/structs.rs @@ -0,0 +1,23 @@ +#[derive(Debug, serde::Serialize)] +pub struct Sorted { + #[allow(dead_code)] + pub repo: Vec, + #[allow(dead_code)] + pub aur: Vec, + #[allow(dead_code)] + pub nf: Vec, +} + +impl Sorted { + pub fn new(repo: Vec, aur: Vec, nf: Vec) -> Self { + let a: Sorted = Sorted { repo, aur, nf }; + a + } +} + +#[derive(Clone, Copy)] +pub struct Options { + pub verbosity: i32, + pub noconfirm: bool, + pub asdeps: bool, +} diff --git a/src/main.rs b/src/main.rs index c38eba5..3a7f7f2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,25 +1,16 @@ -mod mods; -use mods::{ - clearcache::clearcache, - clone::clone, - database::create_database, - help::help, - inssort::{inssort, inssort_from_file}, - install::install, - purge::{purge, purge_from_file}, - search::{a_search, r_search}, - stat_database::*, - statpkgs::*, - strs::err_rec, - strs::err_unrec, - strs::inf, - uninstall::{uninstall, uninstall_from_file}, - update::update, - upgrade::upgrade, - ver::ver, - xargs::*, -}; -use std::{env, process::exit}; +use std::io; +use std::process::{exit, Command}; + +use clap::{App, AppSettings, Arg, ArgMatches, ArgSettings, Shell, SubCommand}; + +use crate::internal::{crash, info, init, log, sort, structs::Options}; + +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + +mod database; +mod internal; +mod operations; fn main() { extern "C" { @@ -27,92 +18,287 @@ fn main() { } if unsafe { geteuid() } == 0 { - // check if user runs ame as root - err_unrec( - "Do not run ame as root! this can cause serious damage to your system!".to_string(), - ); + crash("Running amethyst as root is disallowed as it can lead to system breakage. Instead, amethyst will prompt you when it needs superuser permissions".to_string(), 1); } - let args: Vec = env::args().skip(1).collect(); - let mut pkgs: Vec = env::args().skip(2).collect(); - - if args.is_empty() { - help(); - exit(1); + fn build_app() -> App<'static, 'static> { + let app = App::new("Amethyst") + .version(env!("CARGO_PKG_VERSION")) + .about(env!("CARGO_PKG_DESCRIPTION")) + .arg( + Arg::with_name("verbose") + .short("v") + .long("verbose") + .multiple(true) + .set(ArgSettings::Global) + .help("Sets the level of verbosity"), + ) + .arg( + Arg::with_name("noconfirm") + .long("noconfirm") + .set(ArgSettings::Global) + .help("Complete operation without prompting user"), + ) + .subcommand( + SubCommand::with_name("install") + .about( + "Installs a package from either the AUR or the PacMan-defined repositories", + ) + .aliases(&["-S", "ins"]) + .arg( + Arg::with_name("package(s)") + .help("The name of the package(s) to install") + .required(true) + .multiple(true) + .index(1), + ), + ) + .subcommand( + SubCommand::with_name("remove") + .about("Removes a previously installed package") + .aliases(&["-R", "-Rs", "rm"]) + .arg( + Arg::with_name("package(s)") + .help("The name of the package(s) to remove") + .required(true) + .multiple(true) + .index(1), + ), + ) + .subcommand( + SubCommand::with_name("search") + .about("Searches for the relevant packages in both the AUR and repos") + .aliases(&["-Ss", "sea"]) + .arg( + Arg::with_name("aur") + .short("a") + .long("aur") + .help("Search only the AUR for the package"), + ) + .arg( + Arg::with_name("repo") + .short("r") + .long("repo") + .help("Searches only local repos for the package"), + ) + .arg( + Arg::with_name("package(s)") + .help("The name of the package to search for") + .required(true) + .multiple(false) + .index(1), + ), + ) + .subcommand( + SubCommand::with_name("query") + .about("Queries installed packages") + .aliases(&["-Q", "ls"]) + .arg( + Arg::with_name("aur") + .short("a") + .help("Lists AUR/foreign packages"), + ) + .arg( + Arg::with_name("repo") + .short("r") + .help("Lists repo/native packages"), + ), + ) + .subcommand( + SubCommand::with_name("upgrade") + .about("Upgrades locally installed packages to their latest versions") + .aliases(&["-Syu", "upg"]), + ) + .subcommand( + SubCommand::with_name("compgen") + .about("Generates shell completions for given shell (bash by default)") + .aliases(&["-G", "cg"]) + .arg( + Arg::with_name("shell") + .help("The name of the shell you want to generate completions for") + .possible_values(&["bash", "fish", "zsh", "pwsh", "elvish"]) + .required(true), + ), + ) + .settings(&[ + AppSettings::GlobalVersion, + AppSettings::VersionlessSubcommands, + AppSettings::ArgRequiredElseHelp, + AppSettings::InferSubcommands, + ]); + app } - let file = format!("{}/.local/share/ame/aur_pkgs.db", env::var("HOME").unwrap()); - if !std::path::Path::new(&file).exists() { - create_database(); + let matches = build_app().get_matches(); + + let verbosity: i32 = matches.occurrences_of("verbose") as i32; + let noconfirm: bool = matches.is_present("noconfirm"); + + let options = Options { + verbosity, + noconfirm, + asdeps: false, + }; + + init(options); + + fn collect_matches(a: &ArgMatches) -> Vec { + a.subcommand() + .1 + .unwrap() + .values_of("package(s)") + .unwrap() + .into_iter() + .map(|s| s.to_string()) + .collect() } - let oper = &args[0]; - let noconfirm: bool = noconf(&args); + if let true = matches.is_present("install") { + let packages = collect_matches(&matches); + let sorted = sort(&packages, options); - argssort(&mut pkgs); - match oper.as_str() { - // match oper - "-S" | "-Sn" | "ins" => { - inssort(noconfirm, false, pkgs); // install - } - "-Sl" | "-Sln" | "insl" => { - inssort_from_file(noconfirm, false, &pkgs[0]); // install from file - } - "-B" | "-Bn" | "build" => { - rebuild(noconfirm); // install as a dependency - } - "-R" | "-Rn" | "rm" => { - uninstall(noconfirm, pkgs); // uninstall - } - "-Rs" | "-Rsn" | "purge" => { - purge(noconfirm, pkgs); // purge - } - "-Rl" | "-Rln" | "rml" => { - uninstall_from_file(noconfirm, &pkgs[0]); // uninstall from file - } - "-Rsl" | "-Rsln" | "purgel" => { - purge_from_file(noconfirm, &pkgs[0]); // purge from file + info(format!( + "Attempting to install packages: {}", + packages.join(", ") + )); + + if !sorted.repo.is_empty() { + operations::install(sorted.repo, options); } - "-Syu" | "-Syun" | "upg" => { - upgrade(noconfirm); // upgrade + if !sorted.aur.is_empty() { + operations::aur_install(sorted.aur, options); } - "-Sy" | "upd" => { - update(); // update + if !sorted.nf.is_empty() { + log(format!( + "Couldn't find packages: {} in repos or the AUR", + sorted.nf.join(", ") + )); } - "-Ss" | "sea" => { - r_search(&args[1]); // search for packages in the repository - a_search(&args[1]); // search for packages in the aur + exit(0); + } + + if let true = matches.is_present("remove") { + let packages = collect_matches(&matches); + info(format!("Uninstalling packages: {}", &packages.join(", "))); + operations::uninstall(packages, options); + exit(0); + } + + if let true = matches.is_present("upgrade") { + info("Performing system upgrade".to_string()); + operations::upgrade(options); + exit(0); + } + + if let true = matches.is_present("search") { + let packages = collect_matches(&matches); + if matches + .subcommand_matches("search") + .unwrap() + .is_present("aur") + { + info(format!("Searching AUR for {}", &packages[0])); + operations::aur_search(&packages[0], options); } - "-Sa" | "aursea" => { - a_search(&args[1]); // search for packages in the aur + if matches + .subcommand_matches("search") + .unwrap() + .is_present("repo") + { + info(format!("Searching repos for {}", &packages[0])); + operations::search(&packages[0], options); } - "-Sr" | "repsea" => { - r_search(&args[1]); // search for packages in the repository + + if !matches + .subcommand_matches("search") + .unwrap() + .is_present("repo") + && !matches + .subcommand_matches("search") + .unwrap() + .is_present("aur") + { + info(format!("Searching AUR and repos for {}", &packages[0])); + operations::search(&packages[0], options); + operations::aur_search(&packages[0], options); } - "-Cc" | "clr" => { - clearcache(); // clear cache + exit(0); + } + + if let true = matches.is_present("query") { + if matches + .subcommand_matches("query") + .unwrap() + .is_present("aur") + { + Command::new("pacman") + .arg("-Qm") + .spawn() + .expect("Something has gone wrong") + .wait() + .unwrap(); } - "-v" | "-V" | "ver" => { - ver(); // version + if matches + .subcommand_matches("query") + .unwrap() + .is_present("repo") + { + Command::new("pacman") + .arg("-Qn") + .spawn() + .expect("Something has gone wrong") + .wait() + .unwrap(); } - "-h" | "help" => { - help(); // help + if !matches + .subcommand_matches("query") + .unwrap() + .is_present("aur") + && !matches + .subcommand_matches("query") + .unwrap() + .is_present("repo") + { + Command::new("pacman") + .arg("-Qn") + .spawn() + .expect("Something has gone wrong") + .wait() + .unwrap(); + Command::new("pacman") + .arg("-Qm") + .spawn() + .expect("Something has gone wrong") + .wait() + .unwrap(); } - _ => { - // if oper is not valid it either passes the args to pacman or prints an error - let pass = runas::Command::new("pacman") - .args(&args) - .status() - .expect("Something has gone wrong."); - - match pass.code() { - Some(1) => { - err_rec(format!("No such operation \"{}\"", args.join(" "))); - inf("Try running \"ame help\" for an overview of how to use ame".to_string()) - } - Some(_) => {} - None => err_unrec("Something has gone terribly wrong.".to_string()), + exit(0); + } + + if let true = &matches.is_present("compgen") { + let mut app = build_app(); + match matches + .subcommand_matches("compgen") + .unwrap() + .value_of("shell") + .unwrap() + { + "bash" => { + app.gen_completions_to("ame", Shell::Bash, &mut io::stdout()); + } + "fish" => { + app.gen_completions_to("ame", Shell::Fish, &mut io::stdout()); + } + "zsh" => { + app.gen_completions_to("ame", Shell::Zsh, &mut io::stdout()); + } + "pwsh" => { + app.gen_completions_to("ame", Shell::PowerShell, &mut io::stdout()); + } + "elvish" => { + app.gen_completions_to("ame", Shell::Elvish, &mut io::stdout()); } + _ => {} } } -} +} \ No newline at end of file diff --git a/src/mods.rs b/src/mods.rs deleted file mode 100644 index f87e8c6..0000000 --- a/src/mods.rs +++ /dev/null @@ -1,17 +0,0 @@ -pub mod clearcache; -pub mod clone; -pub mod database; -pub mod help; -pub mod inssort; -pub mod install; -pub mod purge; -pub mod rpc; -pub mod search; -pub mod stat_database; -pub mod statpkgs; -pub mod strs; -pub mod uninstall; -pub mod update; -pub mod upgrade; -pub mod ver; -pub mod xargs; diff --git a/src/mods/clearcache.rs b/src/mods/clearcache.rs deleted file mode 100644 index 86fcf46..0000000 --- a/src/mods/clearcache.rs +++ /dev/null @@ -1,12 +0,0 @@ -use crate::mods::strs::err_rec; -use std::fs; - -pub fn clearcache() { - // delete all files in cache - let path = format!("{}/.cache/ame/", std::env::var("HOME").unwrap()); - - err_rec("Clearing cache".to_string()); - - fs::remove_dir_all(&path).unwrap(); - fs::create_dir(&path).unwrap(); -} diff --git a/src/mods/clone.rs b/src/mods/clone.rs deleted file mode 100644 index 264cd1d..0000000 --- a/src/mods/clone.rs +++ /dev/null @@ -1,180 +0,0 @@ -use crate::{ - err_unrec, inf, mods::database::add_pkg, mods::purge::purge, mods::rpc::*, mods::strs::prompt, - mods::strs::sec, mods::strs::succ, inssort -}; -use moins::Moins; -use std::{env, fs, path::Path, process::Command}; - -fn uninstall_make_depend(pkg: &str) { - // uninstall make depends of a package - let make_depends = rpcinfo(pkg).make_depends; - - let explicit_packages = Command::new("pacman") - .arg("-Qetq") - .stdout(std::process::Stdio::piped()) - .output() - .expect("Something has gone terribly wrong"); - - let expl_pkgs_parse = String::from_utf8(explicit_packages.stdout).unwrap(); - let expl_pkgs_parse = expl_pkgs_parse.split('\n').collect::>(); - - let mut rem_pkgs: Vec = Vec::new(); - for pkg in expl_pkgs_parse { - for md in &make_depends { - if let false = md.contains(pkg) { - if let false = rem_pkgs.contains(md) { - rem_pkgs.push(md.as_str().to_string()); - } - }; - } - } - - if !rem_pkgs.is_empty() { - inf(format!( - "{} installed following make dependencies: {}", - pkg, - rem_pkgs.join(", ") - )); - let remove = prompt("Would you like to remove them?".to_string()); - if remove { - purge(true, rem_pkgs); - } - } - succ(format!("Succesfully installed {}", pkg)); -} - -pub fn clone(noconfirm: bool, as_dep: bool, pkg: &str) { - // clone a package from aur - let cachedir = format!("{}/.cache/ame", env::var("HOME").unwrap()); - let path = Path::new(&cachedir); - let pkgdir = format!("{}/{}", &cachedir, &pkg); - let search = rpcsearch(pkg).results; - let package = search.first().unwrap(); - if search.is_empty() { - err_unrec("No matching AUR packages found".to_string()); - } - - let url = format!("https://aur.archlinux.org/{}.git", pkg); - - if !Path::new(&format!("{}/.cache", env::var("HOME").unwrap())).exists() { - fs::create_dir_all(format!("{}/.cache", env::var("HOME").unwrap())) - .expect("Failed to create ~/.cache directory"); - } - if !path.is_dir() { - let cache_result = fs::create_dir(&path); - match cache_result { - Ok(_) => inf("Created cache path (first run)".to_string()), - Err(_) => err_unrec("Could not create cache path".to_string()), - } - } - - inf(format!("Cloning {} ...", pkg)); - - if Path::new(&pkgdir).is_dir() { - let rm_result = fs::remove_dir_all(&pkgdir); - match rm_result { - Ok(_) => inf(format!( - "Package path for {} already found. Removing to reinstall", - pkg - )), - Err(_) => err_unrec(format!( - "Package path for {} already found, but could not remove to reinstall", - pkg - )), - } - } - - let dir_result = fs::create_dir(&pkgdir); - match dir_result { - Ok(_) => inf(format!("Created package directory for {}", pkg)), - Err(_) => err_unrec(format!("Couldn't create package directory for {}", pkg)), - } - - let cd_result = env::set_current_dir(&pkgdir); - match cd_result { - Ok(_) => inf("Entered package directory".to_string()), - Err(_) => err_unrec("Could not enter package directory".to_string()), - } - - sec("Installing AUR package depends".to_string()); - - let clone = std::process::Command::new("git") - .arg("clone") - .arg(&url) - .arg(&pkgdir) - .status() - .expect("Couldn't clone repository"); - match clone.code() { - Some(0) => { - inf(format!("Cloning {} into package directory", pkg)); - } - Some(_) => err_unrec(format!("Failed cloning {} into package directory", pkg)), - _ => err_unrec(format!("Failed cloning {} into package directory", pkg)), - } - if !as_dep { - if !noconfirm { - let pkgbuild = prompt("View PKGBUILD?".to_string()); - - if pkgbuild { - let mut pkgbld = fs::read_to_string(format!("{}/PKGBUILD", &pkgdir)).unwrap(); - Moins::run(&mut pkgbld, None); - } - } - - sec(format!("Installing {} ...", pkg)); - if noconfirm { - let install_result = Command::new("makepkg") - .arg("-si") - .arg("--noconfirm") - .arg("--needed") - .status(); - match install_result { - Ok(_) => { - uninstall_make_depend(pkg); - let vec = vec![pkg]; - add_pkg(false, &vec); - } - Err(_) => { - err_unrec(format!("Couldn't install {}", pkg)); - } - }; - } else { - let install_result = Command::new("makepkg") - .arg("-si") - .arg("--needed") - .status() - .expect("Couldn't call makepkg"); - match install_result.code() { - Some(0) => { - uninstall_make_depend(pkg); - let vec = vec![pkg]; - add_pkg(false, &vec); - } - Some(_) => { - err_unrec(format!("Couldn't install {}", pkg)); - } - None => { - err_unrec(format!("Couldn't install {}", pkg)); - } - }; - } - } else { - sec(format!("Installing {} ...", pkg)); - let install_result = Command::new("makepkg") - .arg("-si") - .arg("--noconfirm") - .arg("--needed") - .arg("--asdeps") - .status(); - match install_result { - Ok(_) => { - uninstall_make_depend(pkg); - let vec = vec![pkg]; - add_pkg(false, &vec); - } - Err(_) => { - err_unrec(format!("Couldn't install {}", pkg)); - } - }; - } -} diff --git a/src/mods/help.rs b/src/mods/help.rs deleted file mode 100644 index 5cd2bee..0000000 --- a/src/mods/help.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::mods::strs::{err_rec, inf}; - -pub fn help() { - // print help message - println!(); - inf("Usage:".to_string()); - println!( - " -ame -S(n) / ins - install a package -ame -R(n) / rm - remove a package -ame -Rs(n) / purge - remove a package with it dependencies -ame -Syu(n) / upg - upgrade all packages to latest version -ame -Ss / sea - search for a package -ame -Sa / aursea - search for a package in the aur -ame -Sr / repsea - search for a package in the repos -ame -v / ver - contributors and version info -ame -h / help - display this help message - -ame - passes said flags to be processed by pacman" - ); - println!(); - err_rec("Appending 'n' where (n) is present passes '--noconfirm' to pacman. Use at your own risk. (alternatively, using '--noconfirm' as a flag works too.)".to_string()); - println!(); -} diff --git a/src/mods/inssort.rs b/src/mods/inssort.rs deleted file mode 100644 index d24f97f..0000000 --- a/src/mods/inssort.rs +++ /dev/null @@ -1,208 +0,0 @@ -use crate::{clone, err_unrec, install, mods::strs::sec, mods::rpc::*}; -use regex::Regex; -use std::process::{Command, Stdio}; - -pub fn inssort(noconfirm: bool, as_dep: bool, pkgs: Vec) { - // TODO: understand what the fuck is actually going on here - let mut repo = vec![]; - let mut aur = vec![]; - let re = Regex::new(r"(\S+)((?:>=|<=|>|<)\S+$)").unwrap(); - let reg = Regex::new(r"((?:>=|<=|>|<)\S+$)").unwrap(); - for pkg in pkgs { - match pkg.contains('/') { - true => match pkg.split('/').collect::>()[0] == "aur" { - true => { - aur.push(pkg.split('/').collect::>()[1].to_string()); - } - false => { - let out = Command::new("bash") - .arg("-c") - .arg(format!( - "pacman -Sl {} | grep {}", - pkg.split('/').collect::>()[0], - pkg.split('/').collect::>()[1] - )) - .stdout(Stdio::null()) - .status() - .expect("Something has gone wrong."); - match out.code() { - Some(0) => repo.push(reg.replace_all(&pkg, "").to_string()), - Some(1) => err_unrec(format!( - "Package {} not found in repository {}", - pkg.split('/').collect::>()[1], - pkg.split('/').collect::>()[0] - )), - Some(_) => err_unrec("Something has gone terribly wrong".to_string()), - None => err_unrec("Process terminated".to_string()), - } - } - }, - false => { - let caps = re.captures(&pkg); - match caps { - Some(_) => { - let out = Command::new("pacman") - .arg("-Ss") - .arg(format!( - "^{}$", - caps.unwrap().get(1).map_or("", |m| m.as_str()) - )) - .stdout(Stdio::null()) - .status() - .expect("Something has gone wrong."); - match out.code() { - Some(0) => repo.push(reg.replace_all(&pkg, "").to_string()), - Some(1) => aur.push(pkg), - Some(_) => err_unrec("Something has gone terribly wrong".to_string()), - None => err_unrec("Process terminated".to_string()), - } - } - None => { - let out = Command::new("pacman") - .arg("-Ss") - .arg(format!("^{}$", &pkg)) - .stdout(Stdio::null()) - .status() - .expect("Something has gone wrong."); - match out.code() { - Some(0) => repo.push(pkg), - Some(1) => aur.push(pkg), - Some(_) => err_unrec("Something has gone terribly wrong".to_string()), - None => err_unrec("Process terminated".to_string()), - } - } - } - } - } - } - if !as_dep { - if !repo.is_empty() { - sec(format!("Installing repo packages: {}", &repo.join(", "))); - install(noconfirm, false, &repo.join(" ")); - } - - for a in aur { - sec(format!("Couldn't find {} in repos. Searching AUR", a)); - let md = &rpcinfo(&a).make_depends; - inssort(noconfirm, true, md.to_vec()); - clone(noconfirm, false, &a); - } - } else { - if !repo.is_empty() { - sec(format!("Installing repo packages: {}", &repo.join(", "))); - install(noconfirm, true, &repo.join(" ")); - } - - for a in aur { - sec(format!("Couldn't find {} in repos. Searching AUR", a)); - let md = &rpcinfo(&a).make_depends; - inssort(noconfirm, true, md.to_vec()); - clone(noconfirm, true, &a); - } - } -} - -pub fn inssort_from_file(noconfirm: bool, as_dep: bool, file: &str) { - // same thing as above but with a list of packages from a file - let mut pkgs: Vec = Vec::new(); - let contents = std::fs::read_to_string(&file).expect("Couldn't read file"); - for line in contents.lines() { - pkgs.push(line.to_string()); - } - let mut repo = vec![]; - let mut aur = vec![]; - let re = Regex::new(r"(\S+)((?:>=|<=)\S+$)").unwrap(); - let reg = Regex::new(r"((?:>=|<=)\S+$)").unwrap(); - for pkg in pkgs { - match pkg.contains('/') { - true => match pkg.split('/').collect::>()[0] == "aur" { - true => { - aur.push(pkg.split('/').collect::>()[1].to_string()); - } - false => { - let out = Command::new("bash") - .arg("-c") - .arg(format!( - "pacman -Sl {} | grep {}", - pkg.split('/').collect::>()[0], - pkg.split('/').collect::>()[1] - )) - .stdout(Stdio::null()) - .status() - .expect("Something has gone wrong."); - match out.code() { - Some(0) => repo.push(reg.replace_all(&pkg, "").to_string()), - Some(1) => err_unrec(format!( - "Package {} not found in repository {}", - pkg.split('/').collect::>()[1], - pkg.split('/').collect::>()[0] - )), - Some(_) => err_unrec("Something has gone terribly wrong".to_string()), - None => err_unrec("Process terminated".to_string()), - } - } - }, - false => { - let caps = re.captures(&pkg); - match caps { - Some(_) => { - let out = Command::new("pacman") - .arg("-Ss") - .arg(format!( - "^{}$", - caps.unwrap().get(1).map_or("", |m| m.as_str()) - )) - .stdout(Stdio::null()) - .status() - .expect("Something has gone wrong."); - match out.code() { - Some(0) => repo.push(reg.replace_all(&pkg, "").to_string()), - Some(1) => aur.push(pkg), - Some(_) => err_unrec("Something has gone terribly wrong".to_string()), - None => err_unrec("Process terminated".to_string()), - } - } - None => { - let out = Command::new("pacman") - .arg("-Ss") - .arg(format!("^{}$", &pkg)) - .stdout(Stdio::null()) - .status() - .expect("Something has gone wrong."); - match out.code() { - Some(0) => repo.push(pkg), - Some(1) => aur.push(pkg), - Some(_) => err_unrec("Something has gone terribly wrong".to_string()), - None => err_unrec("Process terminated".to_string()), - } - } - } - } - } - } - if !as_dep { - if !repo.is_empty() { - sec(format!("Installing repo packages: {}", &repo.join(", "))); - install(noconfirm, false, &repo.join(" ")); - } - - for a in aur { - sec(format!("Couldn't find {} in repos. Searching AUR", a)); - let md = &rpcinfo(&a).make_depends; - inssort(noconfirm, true, md.to_vec()); - clone(noconfirm, false, &a); - } - } else { - if !repo.is_empty() { - sec(format!("Installing repo packages: {}", &repo.join(", "))); - install(noconfirm, true, &repo.join(" ")); - } - - for a in aur { - sec(format!("Couldn't find {} in repos. Searching AUR", a)); - let md = &rpcinfo(&a).make_depends; - inssort(noconfirm, true, md.to_vec()); - clone(noconfirm, true, &a); - } - } -} diff --git a/src/mods/install.rs b/src/mods/install.rs deleted file mode 100644 index 2c13edf..0000000 --- a/src/mods/install.rs +++ /dev/null @@ -1,59 +0,0 @@ -use crate::mods::database::add_pkg; -use crate::mods::strs::{err_unrec, succ}; -use runas::Command; - -pub fn install(noconfirm: bool, as_dep: bool, pkg: &str) { - // install a package - let pkgs: Vec<&str> = pkg.split(' ').collect(); - if !as_dep { - if noconfirm { - let result = Command::new("pacman") - .arg("-S") - .arg("--noconfirm") - .arg("--needed") - .args(&pkgs) - .status() - .expect("Couldn't call pacman"); - match result.code() { - Some(0) => { - succ(format!("Succesfully installed packages: {}", pkg)); - add_pkg(true, &pkgs); - } - Some(_) => err_unrec(format!("Couldn't install packages: {}", pkg)), - None => err_unrec(format!("Couldn't install packages: {}", pkg)), - }; - } else { - let result = Command::new("pacman") - .arg("-S") - .arg("--needed") - .args(&pkgs) - .status() - .expect("Couldn't call pacman"); - match result.code() { - Some(0) => { - succ(format!("Succesfully installed packages: {}", pkg)); - add_pkg(true, &pkgs); - } - Some(_) => err_unrec(format!("Couldn't install packages: {}", pkg)), - None => err_unrec(format!("Couldn't install packages: {}", pkg)), - }; - } - } else { - let result = Command::new("pacman") - .arg("-S") - .arg("--noconfirm") - .arg("--needed") - .arg("--asdeps") - .args(&pkgs) - .status() - .expect("Couldn't call pacman"); - match result.code() { - Some(0) => { - succ(format!("Succesfully installed packages: {}", pkg)); - add_pkg(true, &pkgs); - } - Some(_) => err_unrec(format!("Couldn't install packages: {}", pkg)), - None => err_unrec(format!("Couldn't install packages: {}", pkg)), - }; - } -} diff --git a/src/mods/purge.rs b/src/mods/purge.rs deleted file mode 100644 index 6879507..0000000 --- a/src/mods/purge.rs +++ /dev/null @@ -1,121 +0,0 @@ -use crate::mods::{ - database::rem_pkg, - strs::{err_rec, err_unrec, sec, succ}, -}; -use runas::Command; -use std::{fs, path::Path}; - -pub fn purge(noconfirm: bool, pkgs: Vec) { - // purge packages - sec(format!( - "Attempting to uninstall packages: {}", - &pkgs.join(" ") - )); - if noconfirm { - let result = Command::new("pacman") - .arg("-Rsu") - .args(&pkgs) - .arg("--noconfirm") - .status() - .expect("Couldn't call pacman"); - match result.code() { - Some(0) => { - succ(format!( - "Succesfully uninstalled packages: {}", - &pkgs.join(" ") - )); - rem_pkg(&pkgs); - } - Some(_) => err_rec(format!("Couldn't uninstall packages: {}", &pkgs.join(" "))), - None => err_rec(format!("Couldn't uninstall packages: {}", &pkgs.join(" "))), - }; - } else { - let result = Command::new("pacman") - .arg("-Rsu") - .args(&pkgs) - .status() - .expect("Couldn't call pacman"); - match result.code() { - Some(0) => { - succ(format!( - "Succesfully uninstalled packages: {}", - &pkgs.join(" ") - )); - rem_pkg(&pkgs); - } - Some(_) => err_rec(format!("Couldn't uninstall packages: {}", &pkgs.join(" "))), - None => err_rec(format!("Couldn't uninstall packages: {}", &pkgs.join(" "))), - }; - } - for pkg in &pkgs { - let pkgdir = format!("{}/.cache/ame/{}", std::env::var("HOME").unwrap(), pkg); - let path = Path::new(&pkgdir); - if path.is_dir() { - let rm_result = fs::remove_dir_all(&path); - match rm_result { - Ok(_) => succ(format!("Removed AUR cache directory for {}", pkg)), - Err(_) => err_unrec(format!("Failed to remove AUR cache directory for {}", pkg)), - }; - } - } -} - -pub fn purge_from_file(noconfirm: bool, file: &str) { - // purge packages from list of packages - let mut pkgs: Vec = Vec::new(); - let contents = std::fs::read_to_string(&file).expect("Couldn't read file"); - for line in contents.lines() { - pkgs.push(line.to_string()); - } - sec(format!( - "Attempting to uninstall packages: {}", - &pkgs.join(" ") - )); - if noconfirm { - let result = Command::new("pacman") - .arg("-Rsu") - .args(&pkgs) - .arg("--noconfirm") - .status() - .expect("Couldn't call pacman"); - match result.code() { - Some(0) => { - succ(format!( - "Succesfully uninstalled packages: {}", - &pkgs.join(" ") - )); - rem_pkg(&pkgs); - } - Some(_) => err_rec(format!("Couldn't uninstall packages: {}", &pkgs.join(" "))), - None => err_rec(format!("Couldn't uninstall packages: {}", &pkgs.join(" "))), - }; - } else { - let result = Command::new("pacman") - .arg("-Rsu") - .args(&pkgs) - .status() - .expect("Couldn't call pacman"); - match result.code() { - Some(0) => { - succ(format!( - "Succesfully uninstalled packages: {}", - &pkgs.join(" ") - )); - rem_pkg(&pkgs); - } - Some(_) => err_rec(format!("Couldn't uninstall packages: {}", &pkgs.join(" "))), - None => err_rec(format!("Couldn't uninstall packages: {}", &pkgs.join(" "))), - }; - } - for pkg in &pkgs { - let pkgdir = format!("{}/.cache/ame/{}", std::env::var("HOME").unwrap(), pkg); - let path = Path::new(&pkgdir); - if path.is_dir() { - let rm_result = fs::remove_dir_all(&path); - match rm_result { - Ok(_) => succ(format!("Removed AUR cache directory for {}", pkg)), - Err(_) => err_unrec(format!("Failed to remove AUR cache directory for {}", pkg)), - }; - } - } -} diff --git a/src/mods/rpc.rs b/src/mods/rpc.rs deleted file mode 100644 index be9cc13..0000000 --- a/src/mods/rpc.rs +++ /dev/null @@ -1,39 +0,0 @@ -#[derive(serde::Deserialize, Debug, Clone)] -pub struct Package { - #[serde(rename = "Name")] - pub name: String, - #[serde(rename = "Version")] - pub version: String, - #[serde(rename = "Description")] - pub description: Option, - #[serde(default)] - #[serde(rename = "Depends")] - pub depends: Vec, - #[serde(default)] - #[serde(rename = "MakeDepends")] - pub make_depends: Vec, -} - -#[derive(serde::Deserialize)] -pub struct SearchResults { - pub resultcount: u32, - pub results: Vec, -} - -pub fn rpcinfo(pkg: &str) -> Package { - let res = reqwest::blocking::get(&format!( - "https://aur.archlinux.org/rpc/?v=5&type=info&arg={}", - pkg - )).unwrap(); - - res.json::().unwrap().results[0].clone() -} - -pub fn rpcsearch(pkg: &str) -> SearchResults { - let res = reqwest::blocking::get(&format!( - "https://aur.archlinux.org/rpc/?v=5&type=search&arg={}", - pkg - )).unwrap(); - - res.json().unwrap() -} diff --git a/src/mods/search.rs b/src/mods/search.rs deleted file mode 100644 index f3c0e31..0000000 --- a/src/mods/search.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::mods::rpc::*; -use crate::mods::strs::{err_rec, err_unrec, succ}; -use ansi_term::Colour; -use std::process::Command; - -pub fn a_search(pkg: &str) { - // search for a package in the AUR - let results = rpcsearch(pkg).results; - - for r in &results { - if results.is_empty() { - err_rec("No matching AUR packages found".to_string()); - } - println!( - "{}{} {}\n {}", - Colour::Cyan.bold().paint("aur/"), - Colour::White.bold().paint(&r.name), - Colour::Green.bold().paint(&r.version), - Colour::White.paint( - r.description - .as_ref() - .unwrap_or(&"No description available".to_string()) - ) - ); - } - if !results.is_empty() { - succ("AUR search successful".to_string()); - } -} - -pub fn r_search(pkg: &str) { - // search for a package in the repositories - let result = Command::new("pacman") - .arg("-Ss") - .arg(&pkg) - .status() - .unwrap(); - match result.code() { - Some(0) => succ("Repo search successful".to_string()), - Some(1) => err_rec("No matching repo packages found".to_string()), - Some(_) => err_unrec("Someting went terribly wrong".to_string()), - None => err_unrec("Couldn't search pacman repos".to_string()), - }; -} diff --git a/src/mods/stat_database.rs b/src/mods/stat_database.rs deleted file mode 100644 index cec1b91..0000000 --- a/src/mods/stat_database.rs +++ /dev/null @@ -1,100 +0,0 @@ -use crate::{err_unrec, inf}; -use std::env; - -pub fn stat_dump_dat() -> Vec { - let file = format!("{}/.local/share/ame/aur_pkgs.db", env::var("HOME").unwrap()); - let connection = sqlite::open(file).unwrap(); - let mut dat_pkgs = Vec::new(); - let result = connection.iterate("SELECT name FROM static_pkgs", |pairs| { - for &(_column, value) in pairs.iter() { - dat_pkgs.push(value.unwrap().to_string()); - } - true - }); - match result { - Ok(_) => { - //inf("Dumped static packages".to_string()); - } - Err(_) => err_unrec("Couldn't dump packages from database".to_string()), - } - dat_pkgs -} - -pub fn stat_get_value(pkg: &str, sear_value: &str) -> bool { - let file = format!("{}/.local/share/ame/aur_pkgs.db", env::var("HOME").unwrap()); - let connection = sqlite::open(file).unwrap(); - let mut return_val = false; - match sear_value { - "name" => { - let result = connection.iterate( - format!("SELECT name FROM static_pkgs WHERE name = \"{}\";", &pkg), - |pairs| { - for &(_column, _value) in pairs.iter() { - return_val = true; - } - return_val - }, - ); - match result { - Ok(_) => {} - Err(_) => err_unrec("Couldn't get value from database".to_string()), - } - return return_val; - } - "update" => { - let result = connection.iterate( - format!("SELECT pin FROM static_pkgs WHERE name = \"{}\";", &pkg), - |pairs| { - for &(_column, _value) in pairs.iter() { - return_val = true; - } - return_val - }, - ); - match result { - Ok(_) => {} - Err(_) => err_unrec("Couldn't get value from database".to_string()), - } - return return_val; - } - _ => return_val = false, - } - return_val -} - -pub fn stat_rem_pkg(static_pkgs: &[String]) { - let file = format!("{}/.local/share/ame/aur_pkgs.db", env::var("HOME").unwrap()); - let connection = sqlite::open(file).unwrap(); - print!("{:?}", static_pkgs); - for i in static_pkgs { - let result = connection.execute(format!( - " - DELETE FROM static_pkgs WHERE name = \"{}\"; - ", - i - )); - match result { - Ok(_) => inf(format!("Removed {} from database", i)), - Err(_) => err_unrec(format!( - "Couldn't remove {} from database (static packages table)", - i - )), - } - } -} - -pub fn stat_add_pkg(update: &str, pkg: &str) { - let file = format!("{}/.local/share/ame/aur_pkgs.db", env::var("HOME").unwrap()); - let connection = sqlite::open(file).unwrap(); - let pin = if update == "true" { 1 } else { 0 }; - let result = connection.execute(format!( - " - INSERT INTO static_pkgs (name, pin) VALUES (\"{}\", {}); - ", - pkg, pin - )); - match result { - Ok(_) => inf(format!("Added {} to database", pkg)), - Err(_) => err_unrec(format!("Couldn't add {} to database", pkg)), - } -} diff --git a/src/mods/statpkgs.rs b/src/mods/statpkgs.rs deleted file mode 100644 index 50db5b5..0000000 --- a/src/mods/statpkgs.rs +++ /dev/null @@ -1,79 +0,0 @@ -use crate::inf; -use crate::{ - err_rec, inssort, stat_add_pkg, stat_dump_dat, stat_get_value, stat_rem_pkg, uninstall, -}; -use std::{env, fs}; - -pub fn rebuild(noconfirm: bool) { - let file = format!("{}/.config/ame/pkgs.toml", env::var("HOME").unwrap()); - let database = fs::read_to_string(&file).expect("Can't Open Database"); - inf("installing crystal config".to_string()); - - let file = format!("{}/.local/share/ame/aur_pkgs.db", env::var("HOME").unwrap()); - let connection = sqlite::open(file).unwrap(); - connection - .execute( - " - CREATE TABLE IF NOT EXISTS static_pkgs (name TEXT, pin INTEGER); - ", - ) - .unwrap(); - - let db_parsed = database.parse::().expect("Invalid Database"); - let mut pkgs = Vec::new(); - if let Some(entry) = db_parsed.as_table() { - for (key, value) in &*entry { - let mut tempvec = Vec::new(); - // println!("{}", key); - // println!("{}", format!("{}",value).replace("update = ", "")); - tempvec.push(key.to_string()); - tempvec.push(format!("{}", value).replace("update = ", "")); - pkgs.push(tempvec); - } - } - let mut pkgs_to_add: Vec> = Vec::new(); - let mut pkgs_to_install: Vec = Vec::new(); - for i in pkgs { - if !stat_get_value(&i[0], "name") { - let tempvec = vec![i[0].to_string(), i[1].to_string()]; - pkgs_to_add.push(tempvec); - pkgs_to_install.push(i[0].to_string()); - } - } - let mut config_no_change = 0; - if !pkgs_to_install.is_empty() { - inf(format!("Installing {}", pkgs_to_install.join(", "))); - inssort(noconfirm, false, pkgs_to_install); - for i in pkgs_to_add { - stat_add_pkg(&i[1], &i[0]); - } - config_no_change += 1; - } - let dat_pkgs = stat_dump_dat(); - - let mut pkgs = Vec::new(); - if let Some(entry) = db_parsed.as_table() { - for (key, _value) in &*entry { - pkgs.push(key); - } - } - - let mut pkgs_to_remove: Vec = Vec::new(); - for i in dat_pkgs { - if !pkgs.contains(&&i) { - pkgs_to_remove.push(i.to_string()); - } - config_no_change += 1; - } - if !pkgs_to_remove.is_empty() { - inf(format!("Removing {}", pkgs_to_remove.join(", "))); - stat_rem_pkg(&pkgs_to_remove); - uninstall(noconfirm, pkgs_to_remove); - } - - if config_no_change != 0 { - inf("Rebuild Complete".to_string()); - } else { - err_rec("Configuration not changed!".to_string()); - } -} diff --git a/src/mods/strs.rs b/src/mods/strs.rs deleted file mode 100644 index 6cc5e50..0000000 --- a/src/mods/strs.rs +++ /dev/null @@ -1,109 +0,0 @@ -use ansi_term::Colour; -use std::{env, io, io::Write, process, string}; -use uwuizer::*; - -pub fn inf(a: string::String) { - // info - if env::var("AME_UWU").unwrap_or_else(|_| "n/a".to_string()) == "YES" { - println!( - "{} {}", - Colour::Purple.paint("❖"), - Colour::White.paint(uwuize!(&a)) - ); - } else { - println!("{} {}", Colour::Purple.paint("❖"), Colour::White.paint(a)); - } -} - -pub fn sec(a: string::String) { - if env::var("AME_UWU").unwrap_or_else(|_| "n/a".to_string()) == "YES" { - println!( - "{} {}", - Colour::Purple.bold().paint("❖"), - Colour::White.bold().paint(uwuize!(&a)) - ); - } else { - println!( - "{} {}", - Colour::Purple.bold().paint("❖"), - Colour::White.bold().paint(a) - ); - } -} - -pub fn succ(a: string::String) { - // success - if env::var("AME_UWU").unwrap_or_else(|_| "n/a".to_string()) == "YES" { - println!( - "{} {}", - Colour::Green.bold().paint("✓"), - Colour::Green.paint(uwuize!(&a)) - ); - } else { - println!( - "{} {}", - Colour::Green.bold().paint("✓"), - Colour::Green.paint(&a) - ); - } -} - -pub fn prompt(a: string::String) -> bool { - // prompt - if env::var("AME_UWU").unwrap_or_else(|_| "n/a".to_string()) == "YES" { - print!( - "{} {} {}", - Colour::Purple.bold().paint("❖"), - Colour::White.bold().paint(uwuize!(&a)), - Colour::White.bold().paint("(Y/n): ") - ); - } else { - print!( - "{} {} {}", - Colour::Purple.bold().paint("❖"), - Colour::White.bold().paint(&a), - Colour::White.bold().paint("(Y/n): ") - ); - } - io::stdout().flush().ok(); - let mut yn: String = String::new(); - let _ = std::io::stdin().read_line(&mut yn); - !(yn.trim() == "n" || yn.trim() == "N" || yn.trim() == "no" || yn.trim() == "No") -} - -pub fn err_unrec(a: string::String) { - // unrecoverable error - if env::var("AME_UWU").unwrap_or_else(|_| "n/a".to_string()) == "YES" { - println!( - "{} {} {}", - Colour::Red.bold().paint(uwuize!("✖ Unrecoverable error:")), - Colour::Red.paint(uwuize!(&a)), - Colour::Red.bold().paint(uwuize!("Terminating.")) - ); - } else { - println!( - "{} {} {}", - Colour::Red.bold().paint("✖ Unrecoverable error:"), - Colour::Red.paint(a), - Colour::Red.bold().paint("Terminating.") - ); - } - process::exit(1); -} - -pub fn err_rec(a: string::String) { - // recoverable error - if env::var("AME_UWU").unwrap_or_else(|_| "n/a".to_string()) == "YES" { - println!( - "{} {}", - Colour::Yellow.bold().paint(uwuize!("⚠ WARNING:")), - Colour::Yellow.paint(uwuize!(&a)) - ); - } else { - println!( - "{} {}", - Colour::Yellow.bold().paint("⚠ WARNING:"), - Colour::Yellow.paint(a) - ); - } -} diff --git a/src/mods/uninstall.rs b/src/mods/uninstall.rs deleted file mode 100644 index 6b04845..0000000 --- a/src/mods/uninstall.rs +++ /dev/null @@ -1,160 +0,0 @@ -use crate::mods::{ - database::rem_pkg, - strs::{err_rec, err_unrec, sec, succ}, -}; -use runas::Command; -use std::{fs, path::Path}; - -pub fn uninstall(noconfirm: bool, pkgs: Vec) { - // uninstall a package - sec(format!( - "Attempting to uninstall packages: {}", - &pkgs.join(" ") - )); - - let important = [ - "base", - "linux", - "linux-firmware", - "systemd-sysvcompat", - "networkmanager", - "man-db", - "man-pages", - "texinfo", - "sudo", - "curl", - "archlinux-keyring", - "btrfs-progs", - "timeshift", - "timeshift-autosnap", - ]; - - let mut overrides: Vec = Vec::new(); - if Path::new("/etc/ame/overrides.conf").exists() { - overrides = fs::read_to_string("/etc/ame/overrides.conf") - .expect("Failed to read overrides.conf") - .lines() - .map(|s| s.to_string()) - .collect(); - } - - let mut matches: Vec = Vec::new(); - for pkg in pkgs.iter() { - for imp in important.iter() { - if pkg == imp && !overrides.contains(pkg) { - matches.push(pkg.to_string()); - } - } - } - if !matches.is_empty() { - err_unrec(format!("The action you called for tries to uninstall packages: {} . This is disallowed by default as these are important system packages. If you fully know what you are doing and would like to uninstall these, please create an override in /etc/ame/overrides.conf.", matches.join(" "))); - } - - if noconfirm { - let result = Command::new("pacman") - .arg("-Ru") - .args(&pkgs) - .arg("--noconfirm") - .status() - .expect("Couldn't call pacman"); - match result.code() { - Some(0) => { - succ(format!( - "Succesfully uninstalled packages: {}", - &pkgs.join(" ") - )); - rem_pkg(&pkgs); - } - Some(_) => err_rec(format!("Couldn't uninstall packages: {}", &pkgs.join(" "))), - None => err_rec(format!("Couldn't uninstall packages: {}", &pkgs.join(" "))), - }; - } else { - let result = Command::new("pacman") - .arg("-Ru") - .args(&pkgs) - .status() - .expect("Couldn't call pacman"); - match result.code() { - Some(0) => { - succ(format!( - "Succesfully uninstalled packages: {}", - &pkgs.join(" ") - )); - rem_pkg(&pkgs); - } - Some(_) => err_rec(format!("Couldn't uninstall packages: {}", &pkgs.join(" "))), - None => err_rec(format!("Couldn't uninstall packages: {}", &pkgs.join(" "))), - }; - } - for pkg in &pkgs { - let pkgdir = format!("{}/.cache/ame/{}", std::env::var("HOME").unwrap(), pkg); - let path = Path::new(&pkgdir); - if path.is_dir() { - let rm_result = fs::remove_dir_all(&path); - match rm_result { - Ok(_) => succ(format!("Removed AUR cache directory for {}", pkg)), - Err(_) => err_unrec(format!("Failed to remove AUR cache directory for {}", pkg)), - }; - } - } -} - -pub fn uninstall_from_file(noconfirm: bool, file: &str) { - // uninstall a package from a list of packages - let mut pkgs: Vec = Vec::new(); - let contents = std::fs::read_to_string(&file).expect("Couldn't read file"); - for line in contents.lines() { - pkgs.push(line.to_string()); - } - sec(format!( - "Attempting to uninstall packages: {}", - &pkgs.join(" ") - )); - if noconfirm { - let result = Command::new("pacman") - .arg("-Ru") - .args(&pkgs) - .arg("--noconfirm") - .status() - .expect("Couldn't call pacman"); - match result.code() { - Some(0) => { - succ(format!( - "Succesfully uninstalled packages: {}", - &pkgs.join(" ") - )); - rem_pkg(&pkgs); - } - Some(_) => err_rec(format!("Couldn't uninstall packages: {}", &pkgs.join(" "))), - None => err_rec(format!("Couldn't uninstall packages: {}", &pkgs.join(" "))), - }; - } else { - let result = Command::new("pacman") - .arg("-Ru") - .args(&pkgs) - .status() - .expect("Couldn't call pacman"); - match result.code() { - Some(0) => { - succ(format!( - "Succesfully uninstalled packages: {}", - &pkgs.join(" ") - )); - rem_pkg(&pkgs); - } - Some(_) => err_rec(format!("Couldn't uninstall packages: {}", &pkgs.join(" "))), - None => err_rec(format!("Couldn't uninstall packages: {}", &pkgs.join(" "))), - }; - } - for pkg in &pkgs { - let pkgdir = format!("{}/.cache/ame/{}", std::env::var("HOME").unwrap(), pkg); - let path = Path::new(&pkgdir); - if path.is_dir() { - let rm_result = fs::remove_dir_all(&path); - match rm_result { - Ok(_) => succ(format!("Removed AUR cache directory for {}", pkg)), - Err(_) => err_unrec(format!("Failed to remove AUR cache directory for {}", pkg)), - }; - } - } -} diff --git a/src/mods/update.rs b/src/mods/update.rs deleted file mode 100644 index 8b76034..0000000 --- a/src/mods/update.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::mods::strs::{err_unrec, sec, succ}; -use runas::Command; - -pub fn update() { - // update the repositories - sec("Syncing package repos".to_string()); - - let result = Command::new("pacman") - .arg("-Sy") - .status() - .expect("Couldn't call pacman"); - match result.code() { - Some(0) => succ("Repos succesfully synced".to_string()), - Some(_) => err_unrec("Couldn't sync package repos".to_string()), - None => err_unrec("Couldn't sync package repos".to_string()), - } -} diff --git a/src/mods/upgrade.rs b/src/mods/upgrade.rs deleted file mode 100644 index 7fdf94f..0000000 --- a/src/mods/upgrade.rs +++ /dev/null @@ -1,213 +0,0 @@ -use crate::{ - err_rec, err_unrec, inf, inssort, mods::database::get_value, mods::rpc::*, mods::strs::prompt, - mods::strs::sec, mods::strs::succ, uninstall, -}; -use runas::Command; -use std::{env, fs, path::Path}; -use toml; - -fn uninstall_make_depend(pkg: &str) { - // uninstall make depends installed by ame itself - let make_depends = rpcinfo(pkg).make_depends; - - if !make_depends.is_empty() { - inf(format!( - "{} installed following make dependencies: {}", - pkg, - make_depends.join(", ") - )); - let remove = prompt("Would you like to remove them?".to_string()); - if remove { - uninstall(true, make_depends); - } - } - succ(format!("Succesfully upgraded {}", pkg)); -} - -pub fn upgrade(noconfirm: bool) { - // upgrade all packages - let homepath = env::var("HOME").unwrap(); - let cachedir = format!("/{}/.cache/ame/", homepath); - let cache_exists = Path::new(&format!("/{}/.cache/ame/", homepath)).is_dir(); - let file = format!("{}/.local/ame/aurPkgs.db", env::var("HOME").unwrap()); - let database = String::new(); - if Path::new(&file).exists() { - let _db = fs::read_to_string(&file).expect("Can't Open Database"); - } else { - let _cdar = fs::create_dir_all(format!("/{}/.local/ame/", homepath)); - match _cdar { - Ok(_) => inf("Created cache directory (previously missing)".to_string()), - Err(_) => err_unrec("Couldn't create cache directory".to_string()), - } - err_rec(String::from("Database wasn't found, creating new one")); - let _dbfile = fs::File::create(&file); - let _db = String::new(); - } - let db_parsed = database.parse::().expect("Invalid Database"); - - if !cache_exists { - let cachecreate = fs::create_dir_all(&cachedir); - match cachecreate { - Ok(_) => inf("Creating cachedir. (didn't exist previously)".to_string()), - Err(_) => err_unrec("Couldn't create cachedir".to_string()), - } - } - sec("Performing system upgrade".to_string()); - if noconfirm { - let result = Command::new("pacman") - .arg("-Syu") - .arg("--noconfirm") - .status() - .expect("Couldn't call pacman"); - match result.code() { - Some(0) => succ("All repo packages upgraded".to_string()), - Some(_) => err_unrec("Couldn't upgrade packages".to_string()), - None => err_unrec("Couldn't upgrade packages".to_string()), - }; - } else { - let result = Command::new("pacman") - .arg("-Syu") - .status() - .expect("Couldn't call pacman"); - match result.code() { - Some(0) => succ("All repo packages upgraded".to_string()), - Some(_) => err_unrec("Couldn't upgrade packages".to_string()), - None => err_unrec("Couldn't upgrade packages".to_string()), - }; - } - - if let Some(entry) = db_parsed.as_table() { - for (key, _value) in &*entry { - let results = rpcsearch(&key.to_string()).results; - let url = format!("https://aur.archlinux.org/{}.git", key); - let package = rpcinfo(&key.to_string()); - let version = get_value(key, "version"); - if results[0].version.contains(&version) { - let keydir = format!("{}{}", &cachedir, &key); - if Path::new(&keydir).is_dir() { - let cd_result = env::set_current_dir(&keydir); - match cd_result { - Ok(_) => inf("Entered package directory".to_string()), - Err(_) => err_unrec("Could not enter package directory".to_string()), - } - inssort(true, true, package.depends.clone()); - - sec(format!("Installing {} ...", &key)); - let install_result = std::process::Command::new("makepkg") - .arg("-si") - .arg("--noconfirm") - .arg("--needed") - .status(); - match install_result { - Ok(_) => { - uninstall_make_depend(key); - } - Err(_) => { - err_unrec(format!("Couldn't install {}", &key)); - } - }; - - sec(format!("Installing {} ...", &key)); - let install_result = std::process::Command::new("makepkg") - .arg("-si") - .arg("--needed") - .status() - .expect("Couldn't call makepkg"); - match install_result.code() { - Some(0) => { - uninstall_make_depend(key); - } - Some(_) => { - err_unrec(format!("Couldn't install {}", &key)); - } - None => { - err_unrec(format!("Couldn't install {}", &key)); - } - }; - } else { - inf(format!("Cloning {} ...", &key)); - - if Path::new(&keydir).is_dir() { - let rm_result = fs::remove_dir_all(&keydir); - match rm_result { - Ok(_) => inf(format!( - "Package path for {} already found. Removing to reinstall", - &key - )), - Err(_) => err_unrec(format!( - "Package path for {} already found, but could not remove to reinstall", - &key - )), - } - } - - let dir_result = fs::create_dir(&keydir); - match dir_result { - Ok(_) => inf(format!("Created package directory for {}", &key)), - Err(_) => { - err_unrec(format!("Couldn't create package directory for {}", &key)) - } - } - - let cd_result = env::set_current_dir(&keydir); - match cd_result { - Ok(_) => inf("Entered package directory".to_string()), - Err(_) => err_unrec("Could not enter package directory".to_string()), - } - - inssort(true, true, package.depends.clone()); - - let clone = std::process::Command::new("git") - .arg("clone") - .arg(&url) - .arg(&keydir) - .status() - .expect("Couldn't clone repo"); - match clone.code() { - Some(0) => { - inf(format!("Cloning {} into package directory", &key)); - } - Some(_) => { - err_unrec(format!("Failed cloning {} into package directory", &key)) - } - _ => err_unrec(format!("Failed cloning {} into package directory", &key)), - } - } - - sec(format!("Installing {} ...", &key)); - let install_result = std::process::Command::new("makepkg") - .arg("-si") - .arg("--noconfirm") - .arg("--needed") - .status(); - match install_result { - Ok(_) => { - uninstall_make_depend(key); - } - Err(_) => { - err_unrec(format!("Couldn't install {}", &key)); - } - }; - sec(format!("Installing {} ...", &key)); - let install_result = std::process::Command::new("makepkg") - .arg("-si") - .arg("--needed") - .status() - .expect("Couldn't call makepkg"); - match install_result.code() { - Some(0) => { - uninstall_make_depend(key); - } - Some(_) => { - err_unrec(format!("Couldn't install {}", &key)); - } - None => { - err_unrec(format!("Couldn't install {}", &key)); - } - }; - } else { - inf(format!("Package {} already up to date", &key)); - } - } - } -} diff --git a/src/mods/ver.rs b/src/mods/ver.rs deleted file mode 100644 index 251802d..0000000 --- a/src/mods/ver.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::inf; -use ansi_term::Colour; - -pub fn ver() { - const VERSION: &str = env!("CARGO_PKG_VERSION"); - - // print version and contributors - println!(); - inf(format!("ame - {}", VERSION)); - println!(); - inf("Contributors:".to_string()); - println!("- axtlos "); - println!("- jnats "); - println!("- jasio "); - println!("- generic "); - println!(); - inf("This software is licensed under the BSD 3-Clause license.".to_string()); - inf("All source code is available at:".to_string()); - println!(); - println!( - "{}", - Colour::Purple - .bold() - .paint("https://git.getcryst.al/crystal/ame") - ); - println!(); -} diff --git a/src/mods/xargs.rs b/src/mods/xargs.rs deleted file mode 100644 index 4728659..0000000 --- a/src/mods/xargs.rs +++ /dev/null @@ -1,13 +0,0 @@ -pub fn noconf(args: &[String]) -> bool { - // noconfirm if user passed --noconfirm or added n to the end of the arg - args.contains(&"--noconfirm".to_string()) || args[0].ends_with(&"n".to_string()) -} - -pub fn argssort(args: &mut Vec) -> &Vec { - // sort the args - if args.contains(&"--noconfirm".to_string()) { - args.retain(|x| x != &"--noconfirm".to_string()); - return args; - } - args -} diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs new file mode 100644 index 0000000..4a8533e --- /dev/null +++ b/src/operations/aur_install.rs @@ -0,0 +1,145 @@ +use std::env; +use std::env::set_current_dir; +use std::fs::remove_dir_all; +use std::path::Path; +use std::process::{Command, Stdio}; + +use crate::{info, log, Options}; +use crate::internal::{crash, prompt}; +use crate::internal::rpc::rpcinfo; + +pub fn aur_install(a: Vec, options: Options) { + let url = crate::internal::rpc::URL; + let cachedir = format!("{}/.cache/ame/", env::var("HOME").unwrap()); + let verbosity = options.verbosity; + let noconfirm = options.noconfirm; + + if verbosity >= 1 { + log(format!("Installing from AUR: {:?}", &a)); + } + + info(format!("Installing packages {} from the AUR", a.join(", "))); + + for package in a { + let rpcres = rpcinfo(package); + + if !rpcres.found { + break; + } + + let pkg = &rpcres.package.as_ref().unwrap().name; + + if verbosity >= 1 { + log(format!("Cloning {} into cachedir", pkg)); + } + + info("Cloning package source".to_string()); + + set_current_dir(Path::new(&cachedir)).unwrap(); + Command::new("git") + .arg("clone") + .arg(format!("{}/{}", url, pkg)) + .stdout(Stdio::null()) + .status() + .expect("Something has gone wrong"); + + if verbosity >= 1 { + log(format!( + "Cloned {} into cachedir, moving on to resolving dependencies", + pkg + )); + log(format!( + "Raw dependencies for package {} are:\n{:?}", + pkg, + rpcres.package.as_ref().unwrap().depends.join(", ") + )); + } + + // dep sorting + info("Sorting dependencies".to_string()); + let sorted = crate::internal::sort(&rpcres.package.as_ref().unwrap().depends, options); + + if verbosity >= 1 { + log(format!( + "Sorted dependencies for {} are:\n{:?}", + pkg, &sorted + )); + } + + let newopts = Options { + verbosity, + noconfirm, + asdeps: true, + }; + + if !sorted.nf.is_empty() { + crash( + format!( + "Could not find dependencies {} for package {}, aborting", + sorted.nf.join(", "), + pkg + ), + 5, + ); + } + + if !noconfirm { + let p1 = prompt( + format!("Would you like to review {}'s PKGBUILD?", pkg), + false, + ); + let editor = env::var("PAGER").unwrap_or_else(|_| "less".parse().unwrap()); + + if p1 { + Command::new(editor) + .arg(format!("{}/PKGBUILD", pkg)) + .spawn() + .unwrap() + .wait() + .unwrap(); + let p2 = prompt(format!("Would you still like to install {}?", pkg), true); + if !p2 { + crash("Not proceeding".to_string(), 6); + } + } + } + + // dep installing + info("Moving on to install dependencies".to_string()); + if !sorted.repo.is_empty() { + crate::operations::install(sorted.repo, newopts); + } + if !sorted.aur.is_empty() { + crate::operations::aur_install(sorted.aur, newopts); + } + + let mut makepkg_args = vec!["-rsic", "--needed"]; + if options.asdeps { + makepkg_args.push("--asdeps") + } + if options.noconfirm { + makepkg_args.push("--noconfirm") + } + + // package building and installing + info("Building time!".to_string()); + set_current_dir(format!("{}/{}", cachedir, pkg)).unwrap(); + let out = Command::new("makepkg") + .args(&makepkg_args) + .status() + .expect("Something has gone wrong"); + + if out.code() != Some(0) { + crash( + format!("Error encountered while installing {}, aborting", pkg), + 7, + ); + } + + set_current_dir(&cachedir).unwrap(); + remove_dir_all(format!("{}/{}", cachedir, &pkg)).unwrap(); + + // pushes package to database + crate::database::add(rpcres.package.unwrap(), options); + } +} diff --git a/src/operations/install.rs b/src/operations/install.rs new file mode 100644 index 0000000..95f1ddf --- /dev/null +++ b/src/operations/install.rs @@ -0,0 +1,38 @@ +use crate::{crash, info, log, Options}; + +pub fn install(a: Vec, options: Options) { + info(format!("Installing packages {} from repos", &a.join(", "))); + let mut opers = vec![]; + if options.noconfirm { + opers.push("--noconfirm".to_string()); + } + if options.asdeps { + opers.push("--asdeps".to_string()); + } + let verbosity = options.verbosity; + if verbosity >= 1 { + log(format!("Installing from repos: {:?}", &a)); + } + + let r = runas::Command::new("pacman") + .arg("-S") + .arg("--needed") + .args(&a) + .args(&opers) + .status() + .expect("Something has gone wrong"); + + if r.code() != Some(0) { + crash( + format!( + "An error occured while installing packages: {}, aborting", + a.join(", ") + ), + 7, + ); + } + + if verbosity >= 1 { + log(format!("Installing packages: {:?} was successful", &a)); + } +} diff --git a/src/operations/mod.rs b/src/operations/mod.rs new file mode 100644 index 0000000..cb6f241 --- /dev/null +++ b/src/operations/mod.rs @@ -0,0 +1,31 @@ +use crate::Options; + +mod aur_install; +mod install; +mod search; +mod uninstall; +mod upgrade; + +pub fn install(a: Vec, options: Options) { + install::install(a, options); +} + +pub fn uninstall(a: Vec, options: Options) { + uninstall::uninstall(a, options); +} + +pub fn search(a: &str, options: Options) { + search::repo_search(a, options); +} + +pub fn aur_install(a: Vec, options: Options) { + aur_install::aur_install(a, options); +} + +pub fn aur_search(a: &str, options: Options) { + search::aur_search(a, options); +} + +pub fn upgrade(options: Options) { + upgrade::upgrade(options); +} diff --git a/src/operations/search.rs b/src/operations/search.rs new file mode 100644 index 0000000..053d9fd --- /dev/null +++ b/src/operations/search.rs @@ -0,0 +1,48 @@ +use std::process::Command; + +use crate::{log, Options}; +use crate::internal::rpc::rpcsearch; + +pub fn aur_search(a: &str, options: Options) { + let verbosity = options.verbosity; + let res = rpcsearch(a.to_string()); + + if verbosity >= 1 { + log(format!( + "Found {} resuls for \"{}\" in AUR", + res.resultcount, a + )); + } + + for r in &res.results { + println!( + "aur/{} {}\n {}", + r.name, + r.version, + r.description + .as_ref() + .unwrap_or(&"No description".to_string()) + ) + } +} + +pub fn repo_search(a: &str, options: Options) { + let verbosity = options.verbosity; + let rs = Command::new("pacman") + .arg("-Ss") + .arg(format!("^{}$", &a)) + .output() + .expect("Something has gone wrong"); + + let str = String::from_utf8(rs.stdout).unwrap(); + + if verbosity >= 1 { + log(format!( + "Found {} results for \"{}\" in repos", + &str.split('\n').count() / 2, + &a + )); + } + + print!("{}", str); +} diff --git a/src/operations/uninstall.rs b/src/operations/uninstall.rs new file mode 100644 index 0000000..208943d --- /dev/null +++ b/src/operations/uninstall.rs @@ -0,0 +1,45 @@ +use std::{env, fs}; +use std::path::Path; + +use crate::{log, Options}; + +pub fn uninstall(mut a: Vec, options: Options) { + let b = a.clone(); + if options.noconfirm { + a.push("--noconfirm".to_string()); + } + let verbosity = options.verbosity; + if verbosity >= 1 { + log(format!("Uninstalling: {:?}", &b)); + } + + let r = runas::Command::new("pacman") + .arg("-Rs") + .args(&a) + .status() + .expect("Something has gone wrong"); + + if let Some(x) = r.code() { + if verbosity >= 1 { + log(format!( + "Uninstalling packages: {:?} exited with code {}", + &b, x + )); + } + } + + for b in a { + crate::database::remove(&b, options); + if Path::new(&format!("{}/.cache/ame/{}", env::var("HOME").unwrap(), b)).exists() { + if verbosity >= 1 { + log("Old cache directory found, deleting".to_string()); + } + fs::remove_dir_all(Path::new(&format!( + "{}/.cache/ame/{}", + env::var("HOME").unwrap(), + b + ))) + .unwrap(); + } + } +} diff --git a/src/operations/upgrade.rs b/src/operations/upgrade.rs new file mode 100644 index 0000000..1a761a0 --- /dev/null +++ b/src/operations/upgrade.rs @@ -0,0 +1,49 @@ +use runas::Command; + +use crate::{info, log, Options}; +use crate::internal::rpc::rpcinfo; +use crate::operations::aur_install::aur_install; + +pub fn upgrade(options: Options) { + let verbosity = options.verbosity; + let noconfirm = options.noconfirm; + + let mut pacman_args = vec!["-Syu"]; + if noconfirm { + pacman_args.push("--noconfirm"); + } + + if verbosity >= 1 { + log("Upgrading repo packages".to_string()); + } + + Command::new("pacman") + .args(&pacman_args) + .status() + .expect("Something has gone wrong"); + + if verbosity >= 1 { + log("Upgrading AUR packages".to_string()); + } + + let res = crate::database::query(options); + + if verbosity >= 1 { + log(format!("{:?}", &res)); + } + + let mut aur_upgrades = vec![]; + for r in res { + let re = r.clone(); + let ver = rpcinfo(r.name); + if ver.package.unwrap().version != r.version { + aur_upgrades.push(re.name); + } + } + + if !aur_upgrades.is_empty() { + aur_install(aur_upgrades, options); + } else { + info("No upgrades available for installed AUR packages".to_string()); + } +}