From c51aa6060ac81cbd258229dce66ae862467009dc Mon Sep 17 00:00:00 2001 From: steven-omaha <35634100+steven-omaha@users.noreply.github.com> Date: Fri, 2 Sep 2022 17:22:07 +0200 Subject: [PATCH] initial commit --- .gitignore | 1 + Cargo.lock | 172 +++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 10 +++ src/args.rs | 10 +++ src/group.rs | 67 +++++++++++++++++++ src/main.rs | 78 ++++++++++++++++++++++ src/package.rs | 121 ++++++++++++++++++++++++++++++++++ 7 files changed, 459 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/args.rs create mode 100644 src/group.rs create mode 100644 src/main.rs create mode 100644 src/package.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..bb9ce11 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,172 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "alpm" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bedac9e074b9368777268cff76c02d8b26d3e567642a52379aadd5c5e204e120" +dependencies = [ + "alpm-sys", + "bitflags", +] + +[[package]] +name = "alpm-sys" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fee1afe3484685ecedf03aefd7904428befed7815687e48aa4b21b3417eeecf0" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "clap" +version = "3.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b71c3ce99b7611011217b366d923f1d0a7e07a92bb2dbf1e84508c673ca3bd" +dependencies = [ + "atty", + "bitflags", + "clap_lex", + "indexmap", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "libc" +version = "0.2.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" + +[[package]] +name = "os_str_bytes" +version = "6.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" + +[[package]] +name = "pacdef" +version = "0.1.0" +dependencies = [ + "alpm", + "clap", +] + +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c2d3c30 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "pacdef" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +alpm = "2.2.1" +clap = "3.2.20" diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..be1c545 --- /dev/null +++ b/src/args.rs @@ -0,0 +1,10 @@ +use clap::Command; +pub(crate) fn get_arg_parser() -> Command<'static> { + let result = Command::new("pacdef") + .about("declarative package manager for Arch Linux") + .version("1.0.0-alpha") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand(Command::new("sync").about("install packages from all imported groups")); + result +} diff --git a/src/group.rs b/src/group.rs new file mode 100644 index 0000000..013a9ec --- /dev/null +++ b/src/group.rs @@ -0,0 +1,67 @@ +use crate::package::Package; +use std::collections::HashSet; +use std::fs::File; +use std::hash::Hash; +use std::io::BufRead; +use std::{io::BufReader, path::PathBuf}; + +const GROUPS_DIR: &str = "/home/ratajc72/.config/pacdef/groups"; + +#[derive(Debug)] +pub(crate) struct Group { + pub(crate) name: String, + pub(crate) packages: HashSet, +} + +impl Group { + pub(crate) fn load_from_dir() -> HashSet { + let mut result = HashSet::new(); + let path = PathBuf::from(GROUPS_DIR); + for entry in path.read_dir().unwrap() { + let file = entry.unwrap(); + let name = file.file_name(); + let f = File::open(file.path()).unwrap(); + let reader = BufReader::new(f); + + let packages = Package::from_lines(reader.lines()); + result.insert(Group { + name: name.into_string().unwrap(), + packages, + }); + + // let content = file.rea + } + result + } +} + +impl PartialOrd for Group { + fn partial_cmp(&self, other: &Self) -> Option { + match self.name.partial_cmp(&other.name) { + Some(core::cmp::Ordering::Equal) => None, + ord => ord, + } + } +} + +impl Ord for Group { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.name.cmp(&other.name) + } +} + +impl Hash for Group { + fn hash(&self, state: &mut H) { + self.name.hash(state); + } +} + +impl PartialEq for Group { + fn eq(&self, other: &Self) -> bool { + self.name == other.name && self.packages == other.packages + } +} + +impl Eq for Group { + fn assert_receiver_is_total_eq(&self) {} +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e03c667 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,78 @@ +use std::io::BufRead; +use std::io::Write; +use std::os::unix::process::CommandExt; +use std::process::exit; +use std::{collections::HashSet, process::Command}; + +use alpm::Alpm; +use args::get_arg_parser; +use group::Group; +use package::Package; + +mod args; +pub(crate) mod group; +pub(crate) mod package; + +fn main() { + let args = get_arg_parser(); + let matches = args.get_matches(); + match matches.subcommand() { + Some(("sync", _)) => install_pacdef_packages(), + s => panic!("{:#?}", s), + } +} + +fn install_pacdef_packages() { + let groups = Group::load_from_dir(); + let packages_hs = groups + .into_iter() + .flat_map(|g| g.packages) + .collect::>(); + + let local_packages = convert_to_pacdef_packages(get_alpm_packages()); + let mut diff: Vec<_> = packages_hs.difference(&local_packages).collect(); + diff.sort_unstable(); + if diff.is_empty() { + exit(0); + } + println!("Would install the following packages:"); + for p in &diff { + println!(" {p}"); + } + println!(); + get_user_confirmation(); + + install_packages(diff); +} + +fn get_user_confirmation() { + print!("Continue? [Y/n] "); + std::io::stdout().flush().unwrap(); + let reply = std::io::stdin().lock().lines().next().unwrap().unwrap(); + if !(reply.is_empty() || reply.to_lowercase().contains('y')) { + exit(0) + } +} + +fn get_alpm_packages() -> HashSet { + let db = Alpm::new("/", "/var/lib/pacman").unwrap(); + db.localdb() + .pkgs() + .iter() + .map(|p| p.name().to_string()) + .collect() +} + +fn convert_to_pacdef_packages(packages: HashSet) -> HashSet { + packages.into_iter().map(Package::from).collect() +} + +fn install_packages(diff: Vec<&Package>) { + let mut cmd = Command::new("paru"); + cmd.arg("-S"); + for p in diff { + cmd.arg(format!("{p}")); + } + dbg!(&cmd); + cmd.exec(); +} diff --git a/src/package.rs b/src/package.rs new file mode 100644 index 0000000..1513ebe --- /dev/null +++ b/src/package.rs @@ -0,0 +1,121 @@ +use std::collections::HashSet; +use std::fmt::Display; +use std::fmt::Write; +use std::fs::File; +use std::hash::Hash; +use std::io::BufReader; +use std::io::Lines; + +#[derive(Debug, Eq, PartialOrd, Ord)] +pub(crate) struct Package { + pub(crate) name: String, + repo: Option, +} + +impl From for Package { + fn from(mut s: String) -> Self { + s.remove_comment(); + s.remove_whitespace(); + let (name, repo) = Self::split_into_name_and_repo(s); + Self { name, repo } + } +} + +impl Package { + pub(crate) fn from_lines(lines: Lines>) -> HashSet { + lines + .into_iter() + .map(|l| Package::from(l.unwrap())) + .collect() + } + + fn split_into_name_and_repo(mut s: String) -> (String, Option) { + match s.find('/') { + None => (s, None), + Some(pos) => { + let mut name = s.split_off(pos); + name = name.split_off(1); + (name, Some(s)) + } + } + } +} + +impl PartialEq for Package { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + && match &self.repo { + None => true, + Some(r) => match &other.repo { + None => true, + Some(r2) => r == r2, + }, + } + } +} + +impl Hash for Package { + fn hash(&self, state: &mut H) { + self.name.hash(state); + } +} + +trait Whitespace { + fn remove_comment(&mut self) {} + fn remove_whitespace(&mut self) {} +} + +impl Whitespace for String { + fn remove_comment(&mut self) { + match self.find('#') { + None => (), + Some(idx) => self.truncate(idx), + } + } + + fn remove_whitespace(&mut self) { + match self.find(char::is_whitespace) { + None => (), + Some(idx) => self.truncate(idx), + } + } +} + +impl Display for Package { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.repo { + None => (), + Some(repo) => { + f.write_str(repo)?; + f.write_char('/')?; + } + } + f.write_str(&self.name) + } +} + +#[cfg(test)] +mod tests { + use super::Package; + + #[test] + fn split_into_name_and_repo() { + let x = "repo/name".to_string(); + let (name, repo) = Package::split_into_name_and_repo(x); + assert_eq!(name, "name"); + assert_eq!(repo, Some("repo".to_string())); + + let x = "something".to_string(); + let (name, repo) = super::Package::split_into_name_and_repo(x); + assert_eq!(name, "something"); + assert_eq!(repo, None); + } + + #[test] + fn from() { + let x = "myrepo/somepackage # ".to_string(); + let p = Package::from(x); + assert_eq!(p.name, "somepackage"); + assert_eq!(p.repo, Some("myrepo".to_string())); + } +}