diff --git a/Cargo.toml b/Cargo.toml
index 9398ff4..d1c1cdb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,5 +11,6 @@ description = "A fast and efficient aur helper."
clap = "2.34.0"
regex = "1.5.4"
runas = "0.2.1"
+rusqlite = "0.26.3"
reqwest = { version = "0.11.7", default-features = false, features = [ "blocking", "json", "default-tls" ] }
serde = { version = "1.0.90", default-features = false, features = [ "derive", "serde_derive" ] }
\ No newline at end of file
diff --git a/README.md b/README.md
index ae91791..21cecc4 100644
--- a/README.md
+++ b/README.md
@@ -15,18 +15,13 @@ Made for Crystal, compatible with any Arch-based Linux distribution.
![](screenshot.png)
## 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 |
-
-You can also use any pacman flag!
+| Action | FreeBSD pkg-style alias | Pacman-style flag(s) |
+|----------------------|-------------------------|----------------------|
+| 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 |
+
## How to build:
(Install cargo)
@@ -47,5 +42,4 @@ Clean all build directories:
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/database/add.rs b/src/database/add.rs
index f3a4676..00920db 100644
--- a/src/database/add.rs
+++ b/src/database/add.rs
@@ -1,7 +1,23 @@
+use crate::internal::rpc::Package;
use crate::Options;
+use rusqlite::Connection;
+use std::env;
+use std::path::Path;
-#[allow(dead_code)]
-#[allow(unused_variables)]
-pub fn add(a: String, options: Options) {
- // TODO: the actual database code lmao
+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 {
+ eprintln!("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| {
+ panic!("Failed adding package {} to the database: {}", pkg.name, e);
+ });
}
diff --git a/src/database/initialise.rs b/src/database/initialise.rs
new file mode 100644
index 0000000..73a0e79
--- /dev/null
+++ b/src/database/initialise.rs
@@ -0,0 +1,35 @@
+use crate::Options;
+use rusqlite::Connection;
+use std::env;
+use std::path::Path;
+
+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 {
+ eprintln!("Creating database at {}", &path);
+ }
+
+ let conn =
+ Connection::open(dbpath).expect("Couldn't create database at ~/.local/share/ame/db.sqlite");
+
+ if verbosity >= 1 {
+ eprintln!("Populating database with table")
+ }
+
+ 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| {
+ panic!("Couldn't initialise database: {}", e);
+ });
+}
diff --git a/src/database/mod.rs b/src/database/mod.rs
index 8923ff6..f37d309 100644
--- a/src/database/mod.rs
+++ b/src/database/mod.rs
@@ -1,8 +1,23 @@
+use crate::internal::rpc::Package;
use crate::Options;
mod add;
+mod initialise;
+mod query;
+mod remove;
-#[allow(dead_code)]
-pub fn add(a: String, options: Options) {
+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(a: &str, options: Options) -> Vec {
+ query::query(a, options)
+}
diff --git a/src/database/query.rs b/src/database/query.rs
new file mode 100644
index 0000000..b27416a
--- /dev/null
+++ b/src/database/query.rs
@@ -0,0 +1,64 @@
+use crate::internal::rpc::Package;
+use crate::Options;
+use rusqlite::Connection;
+use std::env;
+use std::path::Path;
+
+pub fn query(a: &str, options: Options) -> Vec {
+ let verbosity = options.verbosity;
+
+ if verbosity >= 1 {
+ eprintln!("Connecting to database");
+ }
+
+ 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 {
+ eprintln!("Querying database for input")
+ }
+
+ let mut rs = conn
+ .prepare("SELECT name, version, description, depends, make_depends FROM packages WHERE name LIKE \"?\"")
+ .unwrap();
+ let packages_iter = rs
+ .query_map([a], |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 {
+ eprintln!("Retrieved results");
+ }
+
+ let mut results: Vec = vec![];
+
+ for package in packages_iter {
+ results.push(package.unwrap());
+ }
+
+ if verbosity >= 1 {
+ eprintln!("Collected results")
+ }
+
+ results
+}
diff --git a/src/database/remove.rs b/src/database/remove.rs
new file mode 100644
index 0000000..44c8a00
--- /dev/null
+++ b/src/database/remove.rs
@@ -0,0 +1,28 @@
+use crate::Options;
+use rusqlite::Connection;
+use std::env;
+use std::path::Path;
+
+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 {
+ eprintln!("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/initialise.rs b/src/internal/initialise.rs
index 3620092..8e6d355 100644
--- a/src/internal/initialise.rs
+++ b/src/internal/initialise.rs
@@ -20,6 +20,10 @@ pub fn init(options: Options) {
}
}
+ 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 {
diff --git a/src/internal/rpc.rs b/src/internal/rpc.rs
index 9972e76..01e1aa9 100644
--- a/src/internal/rpc.rs
+++ b/src/internal/rpc.rs
@@ -8,11 +8,11 @@ pub struct Package {
pub version: String,
#[serde(rename = "Description")]
pub description: Option,
- #[serde(default)]
#[serde(rename = "Depends")]
- pub depends: Vec,
#[serde(default)]
+ pub depends: Vec,
#[serde(rename = "MakeDepends")]
+ #[serde(default)]
pub make_depends: Vec,
}
@@ -22,26 +22,32 @@ pub struct SearchResults {
pub results: Vec,
}
+#[derive(Clone)]
pub struct InfoResults {
pub found: bool,
- pub package: Option
+ pub package: Option,
}
+pub const URL: &str = "https://aur.archlinux.org/";
+
pub fn rpcinfo(pkg: String) -> InfoResults {
let res = reqwest::blocking::get(&format!(
"https://aur.archlinux.org/rpc/?v=5&type=info&arg={}",
pkg
- )).unwrap().json::().unwrap();
+ ))
+ .unwrap()
+ .json::()
+ .unwrap();
if res.results.is_empty() {
InfoResults {
found: false,
- package: None
+ package: None,
}
} else {
InfoResults {
found: true,
- package: Some(res.results[0].clone())
+ package: Some(res.results[0].clone()),
}
}
}
@@ -50,5 +56,8 @@ pub fn rpcsearch(pkg: String) -> SearchResults {
reqwest::blocking::get(&format!(
"https://aur.archlinux.org/rpc/?v=5&type=search&arg={}",
pkg
- )).unwrap().json().unwrap()
+ ))
+ .unwrap()
+ .json()
+ .unwrap()
}
diff --git a/src/internal/sort.rs b/src/internal/sort.rs
index d8050d8..a1fb26d 100644
--- a/src/internal/sort.rs
+++ b/src/internal/sort.rs
@@ -25,7 +25,6 @@ pub fn sort(input: &[String], options: Options) -> structs::Sorted {
}
for b in a {
- #[cfg(unix)]
let rs = Command::new("pacman")
.arg("-Ss")
.arg(format!("^{}$", &b))
@@ -33,16 +32,6 @@ pub fn sort(input: &[String], options: Options) -> structs::Sorted {
.status()
.expect("Something has gone wrong.");
- #[cfg(windows)]
- let rs = Command::new("pwsh")
- .arg("-nop")
- .arg("-c")
- .arg("exit")
- .arg("1")
- .stdout(Stdio::null())
- .status()
- .expect("Something has gone wrong.");
-
if rpc::rpcinfo(b.to_string()).found {
if verbosity >= 1 {
eprintln!("{} found in AUR.", b);
diff --git a/src/internal/structs.rs b/src/internal/structs.rs
index 39c2a3e..7fd8007 100644
--- a/src/internal/structs.rs
+++ b/src/internal/structs.rs
@@ -19,15 +19,5 @@ impl Sorted {
pub struct Options {
pub verbosity: i32,
pub noconfirm: bool,
-}
-
-impl Options {
- #[allow(dead_code)]
- pub fn new(verbosity: i32, noconfirm: bool) -> Self {
- let a: Options = Options {
- verbosity,
- noconfirm,
- };
- a
- }
+ pub asdeps: bool,
}
diff --git a/src/main.rs b/src/main.rs
index 936496d..baa9e8b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -72,6 +72,11 @@ fn main() {
.index(1),
),
)
+ .subcommand(
+ SubCommand::with_name("upgrade")
+ .about("Upgrades locally installed packages to their latest versions")
+ .aliases(&["-Syu", "upg"]),
+ )
.settings(&[
AppSettings::GlobalVersion,
AppSettings::VersionlessSubcommands,
@@ -85,6 +90,7 @@ fn main() {
let options = Options {
verbosity,
noconfirm,
+ asdeps: false,
};
init(options);
@@ -102,8 +108,12 @@ fn main() {
if let true = matches.is_present("install") {
let sorted = sort(&packages, options);
- operations::install(sorted.repo, options);
- operations::aur_install(sorted.aur, options);
+ if !sorted.repo.is_empty() {
+ operations::install(sorted.repo, options);
+ }
+ if !sorted.aur.is_empty() {
+ operations::aur_install(sorted.aur, options);
+ }
if !sorted.nf.is_empty() {
eprintln!(
"Couldn't find packages: {} in repos or the AUR.",
@@ -118,6 +128,11 @@ fn main() {
exit(0);
}
+ if let true = matches.is_present("upgrade") {
+ operations::upgrade(options);
+ exit(0);
+ }
+
if let true = matches.is_present("search") {
if matches
.subcommand_matches("search")
diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs
index 4f82c10..5ede818 100644
--- a/src/operations/aur_install.rs
+++ b/src/operations/aur_install.rs
@@ -1,7 +1,16 @@
+use crate::internal::rpc::rpcinfo;
use crate::Options;
+use std::env;
+use std::env::set_current_dir;
+use std::fs::remove_dir_all;
+use std::path::Path;
+use std::process::Command;
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;
match verbosity {
0 => {}
1 => {
@@ -10,9 +19,102 @@ pub fn aur_install(a: Vec, options: Options) {
}
_ => {
eprintln!("Installing from AUR:");
- for b in a {
+ for b in &a {
eprintln!("{:?}", b);
}
}
}
+
+ for package in a {
+ let rpcres = rpcinfo(package);
+
+ if !rpcres.found {
+ break;
+ }
+
+ let pkg = &rpcres.package.as_ref().unwrap().name;
+
+ if verbosity >= 1 {
+ eprintln!("Cloning {} into cachedir", pkg);
+ }
+
+ // cloning
+ set_current_dir(Path::new(&cachedir)).unwrap();
+ Command::new("git")
+ .arg("clone")
+ .arg(format!("{}/{}", url, pkg))
+ .status()
+ .expect("Something has gone wrong.");
+
+ if verbosity >= 1 {
+ eprintln!(
+ "Cloned {} into cachedir, moving on to resolving dependencies",
+ pkg
+ );
+ eprintln!(
+ "Raw dependencies for package {} are:\n{:?}",
+ pkg,
+ rpcres.package.as_ref().unwrap().depends.join(", ")
+ )
+ }
+
+ // dep sorting
+ let sorted = crate::internal::sort(&rpcres.package.as_ref().unwrap().depends, options);
+
+ if verbosity >= 1 {
+ eprintln!("Sorted depndencies for {} are:\n{:?}", pkg, &sorted)
+ }
+
+ let newopts = Options {
+ verbosity,
+ noconfirm,
+ asdeps: true,
+ };
+
+ if !sorted.nf.is_empty() {
+ panic!(
+ "Could not find dependencies {} for package {}. Aborting",
+ sorted.nf.join(", "),
+ pkg
+ );
+ }
+
+ if !noconfirm {
+ let editor = env::var("EDITOR").unwrap_or_else(|_| "nano".parse().unwrap());
+
+ Command::new(editor)
+ .arg(format!("{}/PKGBUILD", pkg))
+ .spawn()
+ .unwrap()
+ .wait()
+ .unwrap();
+ }
+
+ // dep installing
+ crate::operations::install(sorted.repo, newopts);
+ 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
+ set_current_dir(format!("{}/{}", cachedir, pkg)).unwrap();
+ Command::new("makepkg")
+ .args(&makepkg_args)
+ .status()
+ .expect("Something has gone wrong.");
+
+ if makepkg_args.contains(&"--asdeps") {
+ 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
index 115b8a3..45796d7 100644
--- a/src/operations/install.rs
+++ b/src/operations/install.rs
@@ -5,6 +5,9 @@ pub fn install(mut a: Vec, options: Options) {
if options.noconfirm {
a.push("--noconfirm".to_string());
}
+ if options.asdeps {
+ a.push("--asdeps".to_string());
+ }
let verbosity = options.verbosity;
match verbosity {
0 => {}
@@ -22,6 +25,7 @@ pub fn install(mut a: Vec, options: Options) {
let r = runas::Command::new("pacman")
.arg("-S")
+ .arg("--needed")
.args(&a)
.status()
.expect("Something has gone wrong.");
diff --git a/src/operations/mod.rs b/src/operations/mod.rs
index 7021e82..cb6f241 100644
--- a/src/operations/mod.rs
+++ b/src/operations/mod.rs
@@ -4,6 +4,7 @@ mod aur_install;
mod install;
mod search;
mod uninstall;
+mod upgrade;
pub fn install(a: Vec, options: Options) {
install::install(a, options);
@@ -24,3 +25,7 @@ pub fn aur_install(a: Vec, options: 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/uninstall.rs b/src/operations/uninstall.rs
index 7fa4864..f95a49e 100644
--- a/src/operations/uninstall.rs
+++ b/src/operations/uninstall.rs
@@ -1,4 +1,6 @@
use crate::Options;
+use std::path::Path;
+use std::{env, fs};
pub fn uninstall(mut a: Vec, options: Options) {
let b = a.clone();
@@ -31,4 +33,19 @@ pub fn uninstall(mut a: Vec, options: Options) {
eprintln!("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 {
+ eprintln!("Old cache directory found, deleting")
+ }
+ 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..c287157
--- /dev/null
+++ b/src/operations/upgrade.rs
@@ -0,0 +1,40 @@
+use crate::internal::rpc::rpcinfo;
+use crate::operations::aur_install::aur_install;
+use crate::Options;
+use runas::Command;
+
+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 {
+ eprintln!("Upgrading repo packages")
+ }
+
+ Command::new("pacman")
+ .args(&pacman_args)
+ .status()
+ .expect("Something has gone wrong.");
+
+ if verbosity >= 1 {
+ eprintln!("Upgrading AUR packages")
+ }
+
+ let res = crate::database::query("\"%\"", options);
+
+ 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);
+ }
+ }
+
+ aur_install(aur_upgrades, options);
+}