New CLI surface, approved for review before merge: - search <query>: case-insensitive name/description substring match - completions <shell>: bash/zsh/fish/elvish/powershell via clap_complete - rollback <pkg>: restore the previously installed version from a local pre-update binary backup (not a network re-fetch — index.json's minisign signature only covers the current published version, so pinning an old version from the server would only be checkable against its unsigned per-version .sha256 sidecar, a materially weaker trust path) - verify [pkg]: recompute installed binaries' sha256 and compare against the hash recorded at install time, not a fresh index lookup (the index only has the latest release's checksum, which may not match what's actually installed) - remove --purge: additionally remove the license dir, desktop entry, and data dir, each gated through the existing confirm() prompt; config is still deliberately left alone - self-update: documented entry point for updating bakery itself - --dry-run: global flag, short-circuits right before install:: install_package in both the install and update paths - download progress: chunked read loop in manifest::fetch_bytes prints periodic \r progress on stderr when Content-Length is present and stderr is a tty - update --all output: "already at X" is now DIM with a neutral glyph instead of GREEN, plus a bold one-line summary count, so unchanged packages don't visually compete with ones that actually changed InstalledPackage gained previous_version and binary_sha256 (both #[serde(default)]) to back rollback/verify. fetch_and_place now returns the verified sha256 instead of discarding it. Also fixes a handful of pre-existing clippy lints in files this touches (manual split_once, &PathBuf-vs-&Path, derivable Default, unnecessary unwrap) surfaced by a clippy version newer than when that code was last touched — confirmed via git stash that they predate this branch. bread- utils has one more of these (suspicious_open_options in singleton.rs) left alone: the mechanical fix would truncate the PID file before the lock-held-by-another-process branch reads its contents, which would break toggle_or_kill's PID lookup, so cargo clippy -p bakery needs --no-deps until that one's fixed with actual thought.
85 lines
2.3 KiB
Rust
85 lines
2.3 KiB
Rust
use clap::ValueEnum;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::fmt;
|
|
use std::str::FromStr;
|
|
|
|
/// Which build of a package bakery follows: the tagged stable release, a
|
|
/// deliberately-promoted beta, or the continuously-published `dev` branch
|
|
/// build. Not to be confused with the *distribution* channel (bakery vs.
|
|
/// pacman) documented in `docs/release-channels.md` — that's an orthogonal,
|
|
/// pre-existing use of the word "channel", which is why this is called a
|
|
/// "track" instead.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize, ValueEnum)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum Track {
|
|
#[default]
|
|
Stable,
|
|
Beta,
|
|
Dev,
|
|
}
|
|
|
|
impl Track {
|
|
pub fn as_str(&self) -> &'static str {
|
|
match self {
|
|
Track::Stable => "stable",
|
|
Track::Beta => "beta",
|
|
Track::Dev => "dev",
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Track {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
f.write_str(self.as_str())
|
|
}
|
|
}
|
|
|
|
impl FromStr for Track {
|
|
type Err = String;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
match s.to_lowercase().as_str() {
|
|
"stable" => Ok(Track::Stable),
|
|
"beta" => Ok(Track::Beta),
|
|
"dev" => Ok(Track::Dev),
|
|
other => Err(format!("unknown track '{other}' — expected stable, beta, or dev")),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn default_is_stable() {
|
|
assert_eq!(Track::default(), Track::Stable);
|
|
}
|
|
|
|
#[test]
|
|
fn display_roundtrips_through_from_str() {
|
|
for track in [Track::Stable, Track::Beta, Track::Dev] {
|
|
let s = track.to_string();
|
|
assert_eq!(s.parse::<Track>().unwrap(), track);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn from_str_is_case_insensitive() {
|
|
assert_eq!("DEV".parse::<Track>().unwrap(), Track::Dev);
|
|
assert_eq!("Beta".parse::<Track>().unwrap(), Track::Beta);
|
|
}
|
|
|
|
#[test]
|
|
fn from_str_rejects_unknown() {
|
|
assert!("nightly".parse::<Track>().is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn json_roundtrip_uses_lowercase() {
|
|
let json = serde_json::to_string(&Track::Dev).unwrap();
|
|
assert_eq!(json, "\"dev\"");
|
|
let back: Track = serde_json::from_str(&json).unwrap();
|
|
assert_eq!(back, Track::Dev);
|
|
}
|
|
}
|