diff --git a/Cargo.lock b/Cargo.lock index d4a9884..74c3e16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,7 +20,7 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "rust-archlinux-update" -version = "0.1.1" +version = "1.0.0" dependencies = [ "colored", ] diff --git a/Cargo.toml b/Cargo.toml index 3d715bd..982d682 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust-archlinux-update" -version = "0.1.1" +version = "1.0.0" edition = "2021" license = "GPL-3" diff --git a/README.md b/README.md index 26ac2ba..749242b 100644 --- a/README.md +++ b/README.md @@ -2,27 +2,42 @@ A program that automates the arch update process with cleanup and auto removal. -***Currently, a WIP. USE AT OWN RISK*** - ## Setup -The current state requires that you build it yourself. The most up-to-date version will be on the development branch. +Download the binary from the releases tab. The pre-compiled binary works on x86_64 only. -Just run `cargo build --release` and copy the executable from `./target/release/rust-archlinux-update` to wherever you -need it. +Recommended Installation: -Distribution on pre-built binaries, cargo, etc. is in the works. +1. Place the binary in the `~/local/bin` folder +2. Run `chmod +x` on the binary +3. Add `/home//local/bin` to your PATH variable + +This installation allows it to be run anywhere on your system. + +If you need a different target please see the next section on compilation. + +## Compilation + +It's simple. + +Steps: + +1. Download the source code. (git clone or release tarball) +2. Ensure that rustup is up-to-date and has the correct toolchain installed and selected. +3. Run `cargo build --release` and find the binary at `./target/release/rust-archlinux-update` ## Usage -The default behavior of the command is to use pacman to run a package update, auto-removal, and cache clear in that -order. +rust-archlinux-updater [OPTIONS] -In the future the command will support AUR helpers like paru and those will need to be activated with a flag. +Options: +-h, --help | Show this help message +-u | preform a regular update +-p | Use paru to update the AUR packages on your system ## Issue Reporting -I don't allow random sign-ups on my gitea instance. Please email me at [luke@lukeh990.io](mailto:luke@lukeh990.io). +I don't allow random sign-ups on my gitea instance. Please email me at . If you have any suggestions for how to better handle issue reporting please email me. diff --git a/src/main.rs b/src/main.rs index 542aa09..65a8f5b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,29 +11,142 @@ This file contains the primary logic of the application */ +use std::{env, error}; + use colored::{ColoredString, Colorize}; use wrappers::pacman; +use crate::wrappers::paru; + mod shell_commands; mod wrappers; +#[derive(Debug, Clone)] +struct RunOptions { + pub help: bool, + pub update: bool, + pub paru: bool, +} + fn main() { println!("{}", copyright_notice()); + match get_run_options() { + Ok(options) => { + if options.help { + print_help_text(); + return; + } + + if options.update { + update(options.paru); + return; + } + + print_help_text(); + } + Err(e) => { + error_println("Failed to detect arguments."); + eprintln!("{}", e); + } + } +} + +fn get_run_options() -> Result> { + let mut args = env::args(); + + // Remove unneeded first entry + if args.next().is_none() { + return Err("No args found".into()); + } + + let args: Vec = args.collect(); + + let blank_options = RunOptions { + help: false, + update: false, + paru: false, + }; + + let mut options = blank_options.clone(); + + if args.is_empty() { + return Ok(blank_options); + } + + for arg in args { + if arg[..2].eq("--") { + let flag = &arg[2..]; + if flag.eq("help") { + options.help = true; + } else { + return Ok(blank_options); + } + } else if arg[..1].eq("-") { + let mut selected: Vec<&str> = arg[1..].split("").collect(); + + selected.retain_mut(|x| !x.is_empty()); + + for selected_option in selected { + match selected_option { + "h" => options.help = true, + "u" => options.update = true, + "p" => options.paru = true, + _ => return Ok(blank_options), + } + } + } else { + return Ok(blank_options); + } + } + + Ok(options) +} + +fn print_help_text() { + println!( + "rust-archlinux-updater [OPTIONS]\n\n\ + Options:\n\ + -h, --help | Show this help message\n\ + -u | preform a regular update\n\ + -p | Use paru to update the AUR packages on your system" + ); +} + +fn update(paru: bool) { if let Err(e) = pacman::check() { error_println(e.to_string()); return; } else { - notice_println("Pacman is installed\n") + notice_println("Pacman is installed") } + if paru { + if let Err(e) = paru::check() { + error_println(e.to_string()); + return; + } else { + notice_println("Paru is installed") + } + } + + println!(); + notice_println("Running pacman update"); if let Err(e) = pacman::update_all() { error_println(e.to_string()); return; } + if paru { + notice_println("Updating AUR with paru"); + if let Err(e) = paru::update_aur() { + error_println(e.to_string()); + return; + } + } + notice_println("Getting orphaned packages"); let result = match pacman::get_unused() { Ok(result) => result, @@ -60,7 +173,10 @@ fn main() { } fn copyright_notice() -> ColoredString { - "Rust Arch Linux Updater Copyright (C) 2024 Luke Harding \nThis program comes with ABSOLUTELY NO WARRANTY\nThis is free software, and you are welcome to redistribute it under certain conditions\n".italic() + "Rust Arch Linux Updater Copyright (C) 2024 Luke Harding \ + \nThis program comes with ABSOLUTELY NO WARRANTY\ + \nThis is free software, and you are welcome to redistribute it under certain conditions\n" + .italic() } pub fn error_println(msg: impl Into) { diff --git a/src/wrappers/mod.rs b/src/wrappers/mod.rs index 3b08948..89b035d 100644 --- a/src/wrappers/mod.rs +++ b/src/wrappers/mod.rs @@ -12,3 +12,4 @@ */ pub mod pacman; +pub mod paru; diff --git a/src/wrappers/paru.rs b/src/wrappers/paru.rs new file mode 100644 index 0000000..8ff2b09 --- /dev/null +++ b/src/wrappers/paru.rs @@ -0,0 +1,82 @@ +/* + Rust Arch Linux Updater + Copyright (C) 2024 Luke Harding + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + You should have received a copy of the GNU General Public License along with this program. If not, see . +*/ + +/* + wrappers/paru.rs + This module provides a wrapper to make working with paru much easier. +*/ + +use std::{error, fmt, result}; +use std::fmt::Formatter; +use std::path::Path; +use std::process::ExitStatus; + +use crate::shell_commands; + +type Result = result::Result>; + +#[derive(Debug, Clone)] +enum ParuError { + NotInstalled, + ExitCode(i32), + Other(&'static str), +} + +impl fmt::Display for ParuError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let out = match self { + ParuError::NotInstalled => "Paru not installed.".to_string(), + ParuError::ExitCode(code) => format!("Paru exited with an exit code: {}", code), + ParuError::Other(msg) => msg.to_string(), + }; + + write!(f, "ParuError: {}", out) + } +} + +impl error::Error for ParuError {} + +pub fn check() -> Result<()> { + let out = shell_commands::execute_quiet("which", ["paru"])?; + + let path = if out.status.success() { + let mut stdout = out.stdout; + stdout.pop(); // Remove \n from stdout + + String::from_utf8(stdout)? + } else { + String::from("/usr/bin/paru") + }; + + let path = Path::new(&path); + + if !path.exists() { + return Err(ParuError::NotInstalled.into()); + } + + Ok(()) +} + +pub fn update_aur() -> Result<()> { + let exit_status = shell_commands::execute_in_sh("paru -Sua")?; + + check_exit_code(exit_status) +} + +fn check_exit_code(exit_status: ExitStatus) -> Result<()> { + if !exit_status.success() { + let exit_code = match exit_status.code() { + Some(exit_code) => exit_code, + None => return Err(ParuError::Other("No Exit Code Found").into()), + }; + + return Err(ParuError::ExitCode(exit_code).into()); + } + + Ok(()) +}