├── .gitignore ├── src ├── types.rs ├── utils.rs ├── main.rs ├── aur.rs ├── io.rs ├── request.rs ├── help.in ├── package │ └── tests.rs ├── cli │ └── tests.rs ├── app.rs ├── alpm.rs ├── print.rs ├── package.rs └── cli.rs ├── rustfmt.toml ├── .github └── workflows │ ├── config.toml │ └── release.yml ├── Cargo.toml ├── LICENSE ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | debug/ 2 | target/ 3 | **/*.rs.bk 4 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | pub type Str = Box; 2 | pub type Arr = Box<[T]>; 3 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | fn_params_layout = "Compressed" 2 | short_array_element_width_threshold = 0 3 | use_field_init_shorthand = true 4 | use_small_heuristics = "Max" 5 | -------------------------------------------------------------------------------- /.github/workflows/config.toml: -------------------------------------------------------------------------------- 1 | [registries.crates-io] 2 | protocol = "sparse" 3 | 4 | [profile.release] 5 | panic = "abort" 6 | opt-level = 2 7 | strip = true 8 | lto = true 9 | codegen-units = 1 10 | 11 | [build] 12 | rustflags = [ 13 | "-Ctarget-cpu=generic", 14 | ] 15 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{Arr, Str}; 2 | use std::iter; 3 | 4 | pub fn to_arr(input: &[impl AsRef]) -> Arr { 5 | input.iter().map(AsRef::as_ref).map(Str::from).collect() 6 | } 7 | 8 | pub fn contains(list: &[impl AsRef], value: &str) -> bool { 9 | list.iter().any(move |v| v.as_ref() == value) 10 | } 11 | 12 | pub fn str_diff(a: &str, b: &str) -> usize { 13 | iter::zip(a.bytes(), b.bytes()).position(|(a, b)| a != b).unwrap_or(0) 14 | } 15 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod alpm; 2 | mod app; 3 | mod aur; 4 | mod cli; 5 | mod io; 6 | mod package; 7 | mod print; 8 | mod request; 9 | mod types; 10 | mod utils; 11 | 12 | use std::process::ExitCode; 13 | 14 | fn main() -> ExitCode { 15 | match app::run() { 16 | Ok(help) => { 17 | help.then(print::help); 18 | ExitCode::SUCCESS 19 | } 20 | Err(e) => { 21 | print::error(e); 22 | ExitCode::FAILURE 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aur-check-updates" 3 | version = "1.1.8" 4 | edition = "2024" 5 | rust-version = "1.85" 6 | description = "A very basic CLI app for checking updates from Arch User Repository (AUR)" 7 | repository = "https://github.com/HanabishiRecca/aur-check-updates" 8 | license = "MIT" 9 | readme = "README.md" 10 | publish = false 11 | 12 | [profile.release] 13 | panic = "abort" 14 | opt-level = 2 15 | lto = true 16 | codegen-units = 1 17 | 18 | [dependencies] 19 | alpm = "5.0" 20 | curl = "0.4" 21 | serde = { version = "1.0", features = ["derive"] } 22 | serde_json = "1.0" 23 | -------------------------------------------------------------------------------- /src/aur.rs: -------------------------------------------------------------------------------- 1 | use crate::package::Pkg; 2 | use crate::types::{Arr, Str}; 3 | use serde::Deserialize; 4 | use serde_json::Result; 5 | use std::collections::HashMap; 6 | 7 | #[derive(Deserialize)] 8 | struct Info { 9 | #[serde(rename = "Name")] 10 | name: Str, 11 | #[serde(rename = "Version")] 12 | ver: Str, 13 | } 14 | 15 | impl Info { 16 | fn into_kv(self) -> (Str, Str) { 17 | (self.name, self.ver) 18 | } 19 | } 20 | 21 | #[derive(Deserialize)] 22 | struct Response { 23 | results: Arr, 24 | } 25 | 26 | pub fn args(pkgs: &[Pkg]) -> Str { 27 | pkgs.iter().flat_map(|pkg| ["&arg[]=", pkg.name()]).collect() 28 | } 29 | 30 | pub fn parse(data: &str) -> Result> { 31 | let results = serde_json::from_str::(data)?.results; 32 | Ok(results.into_iter().map(Info::into_kv).collect()) 33 | } 34 | -------------------------------------------------------------------------------- /src/io.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{Arr, Str}; 2 | use std::fs::{self, DirEntry}; 3 | use std::io::Result; 4 | use std::path::PathBuf; 5 | 6 | const DB_DIR: &str = "sync"; 7 | const DB_EXT: &str = ".db"; 8 | 9 | macro_rules! err { 10 | ($e: expr) => { 11 | match $e { 12 | Ok(e) => e, 13 | Err(e) => return Some(Err(e)), 14 | } 15 | }; 16 | } 17 | 18 | macro_rules! test { 19 | ($e: expr) => { 20 | ($e).then_some(())? 21 | }; 22 | } 23 | 24 | fn map_entry(entry: Result) -> Option> { 25 | let entry = err!(entry); 26 | test!(err!(entry.metadata()).is_file()); 27 | 28 | let mut name = entry.file_name().into_string().ok()?; 29 | test!(name.ends_with(DB_EXT)); 30 | 31 | let len = name.len().checked_sub(DB_EXT.len())?; 32 | test!(len > 0); 33 | name.truncate(len); 34 | 35 | Some(Ok(Str::from(name))) 36 | } 37 | 38 | pub fn find_repos(dbpath: &str) -> Result> { 39 | let path = PathBuf::from_iter([dbpath, DB_DIR]); 40 | fs::read_dir(path)?.filter_map(map_entry).collect() 41 | } 42 | -------------------------------------------------------------------------------- /src/request.rs: -------------------------------------------------------------------------------- 1 | use crate::types::Arr; 2 | use curl::Error; 3 | use curl::easy::{Easy, HttpVersion}; 4 | use std::io::Read; 5 | use std::time::Duration; 6 | 7 | #[inline(never)] 8 | pub fn send(url: &str, body: &[u8], timeout: u64) -> Result, Error> { 9 | let mut easy = Easy::new(); 10 | easy.url(url)?; 11 | easy.post(true)?; 12 | easy.http_version(HttpVersion::V11)?; 13 | easy.accept_encoding("")?; 14 | easy.fail_on_error(true)?; 15 | easy.tcp_nodelay(true)?; 16 | easy.timeout(Duration::from_millis(timeout))?; 17 | easy.useragent(concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")))?; 18 | easy.post_field_size(body.len() as u64)?; 19 | 20 | let mut cursor = body; 21 | let mut result = Vec::new(); 22 | let mut transfer = easy.transfer(); 23 | 24 | transfer.read_function(move |buf| Ok(cursor.read(buf).unwrap_or(0)))?; 25 | 26 | transfer.write_function(|data| { 27 | result.extend_from_slice(data); 28 | Ok(data.len()) 29 | })?; 30 | 31 | transfer.perform()?; 32 | drop(transfer); 33 | 34 | Ok(Arr::from(result)) 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Hanabishi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/help.in: -------------------------------------------------------------------------------- 1 | {PKG} {VER} 2 | A very basic CLI app for checking updates from Arch User Repository (AUR). 3 | 4 | Usage: 5 | 6 | $ {BIN_NAME} [