use std::collections::HashMap;
macro_rules! string_vec {
($($item:expr),+ $(,)?) => {{
vec![
$( String::from($item), )*
]
}};
}
#[derive(Clone, Debug)]
pub struct ListParser {
regex: regex::Regex,
}
impl ListParser {
pub fn new(pattern: &str) -> Self {
let pattern = pattern
.replace("<package>", "(?<package>[a-zA-Z0-9-_]+)")
.replace(
"<version>",
"(?<version>(\\d+)(\\.\\d+)?(\\.\\d+)?(-[a-z0-9-_+.]+)?)",
)
.replace("<distro>", "(?<distro>[a-zA-Z0-9-_,]+)")
.replace("<tag>", "(?<tag>@[0-9.]+)?")
.replace("<arch>", "(x86_64|amd64|aarch64|arm64)");
ListParser {
regex: regex::Regex::new(&pattern).unwrap(),
}
}
pub fn parse(&self, output: &str) -> HashMap<String, Option<String>> {
let mut packages = HashMap::default();
for line in output.lines() {
let line = line.trim();
if line.starts_with('#') || line.starts_with("//") {
continue;
}
if let Some(caps) = self.regex.captures(line) {
let Some(name) = caps.name("package") else {
continue;
};
packages.insert(
name.as_str().to_string(),
caps.name("version").map(|cap| cap.as_str().to_string()),
);
}
}
packages
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum CommandType {
InstallPackage,
ListPackages,
UpdateIndex,
}
#[derive(Clone, Debug)]
pub enum PromptArgument {
None,
Interactive(String),
Skip(String),
}
#[derive(Clone, Debug)]
pub enum VersionArgument {
None,
Inline(String),
Separate(String),
}
#[derive(Clone, Debug)]
pub struct PackageManagerConfig {
pub commands: HashMap<CommandType, Vec<String>>,
pub list_parser: ListParser,
pub prompt_arg: PromptArgument,
pub prompt_for: Vec<CommandType>,
pub version_arg: VersionArgument,
}
pub(crate) fn apk() -> PackageManagerConfig {
PackageManagerConfig {
commands: HashMap::from_iter([
(CommandType::InstallPackage, string_vec!["apk", "add", "$"]),
(
CommandType::ListPackages,
string_vec!["apk", "list", "--installed"],
),
(CommandType::UpdateIndex, string_vec!["apk", "update"]),
]),
list_parser: ListParser::new("^<package>-<version>"),
prompt_arg: PromptArgument::Interactive("-i".into()),
prompt_for: vec![CommandType::InstallPackage, CommandType::UpdateIndex],
version_arg: VersionArgument::Inline("=".into()),
}
}
pub(crate) fn apt() -> PackageManagerConfig {
PackageManagerConfig {
commands: HashMap::from_iter([
(
CommandType::InstallPackage,
string_vec!["apt", "install", "--install-recommends", "$"],
),
(
CommandType::ListPackages,
string_vec!["apt", "list", "--installed"],
),
(CommandType::UpdateIndex, string_vec!["apt", "update"]),
]),
list_parser: ListParser::new("^<package>/<distro> <version>"),
prompt_arg: PromptArgument::Skip("-y".into()),
prompt_for: vec![CommandType::InstallPackage, CommandType::UpdateIndex],
version_arg: VersionArgument::Inline("=".into()),
}
}
pub(crate) fn brew() -> PackageManagerConfig {
PackageManagerConfig {
commands: HashMap::from_iter([
(
CommandType::InstallPackage,
string_vec!["brew", "install", "$"],
),
(
CommandType::ListPackages,
string_vec!["brew", "list", "--formula", "--versions"],
),
(CommandType::UpdateIndex, string_vec!["brew", "update"]),
]),
list_parser: ListParser::new("^<package><tag> <version>"),
prompt_arg: PromptArgument::None, prompt_for: vec![CommandType::InstallPackage],
version_arg: VersionArgument::Inline("@".into()),
}
}
pub(crate) fn choco() -> PackageManagerConfig {
PackageManagerConfig {
commands: HashMap::from_iter([
(
CommandType::InstallPackage,
string_vec!["choco", "install", "$"],
),
(CommandType::ListPackages, string_vec!["choco", "list"]),
]),
list_parser: ListParser::new("^<package> <version>"),
prompt_arg: PromptArgument::Skip("-y".into()),
prompt_for: vec![CommandType::InstallPackage],
version_arg: VersionArgument::Separate("--version".into()),
}
}
pub(crate) fn dnf() -> PackageManagerConfig {
PackageManagerConfig {
commands: HashMap::from_iter([
(
CommandType::InstallPackage,
string_vec!["dnf", "install", "$"],
),
(
CommandType::ListPackages,
string_vec!["dnf", "list", "installed"],
),
(CommandType::UpdateIndex, string_vec!["dnf", "check-update"]),
]),
list_parser: ListParser::new("^<package>.<arch>\\s+<version>"),
prompt_arg: PromptArgument::Skip("-y".into()),
prompt_for: vec![CommandType::InstallPackage, CommandType::UpdateIndex],
version_arg: VersionArgument::Inline("-".into()),
}
}
pub(crate) fn pacman() -> PackageManagerConfig {
PackageManagerConfig {
commands: HashMap::from_iter([
(
CommandType::InstallPackage,
string_vec!["pacman", "-S", "$"],
),
(CommandType::ListPackages, string_vec!["pacman", "-Q"]),
(CommandType::UpdateIndex, string_vec!["pacman", "-Syy"]),
]),
list_parser: ListParser::new("^<package> <version>"),
prompt_arg: PromptArgument::Skip("--noconfirm".into()),
prompt_for: vec![CommandType::InstallPackage],
version_arg: VersionArgument::Inline(">=".into()),
}
}
pub(crate) fn pkg() -> PackageManagerConfig {
PackageManagerConfig {
commands: HashMap::from_iter([
(
CommandType::InstallPackage,
string_vec!["pkg", "install", "$"],
),
(
CommandType::ListPackages,
string_vec!["pkg", "info", "--all"],
),
(CommandType::UpdateIndex, string_vec!["pkg", "update"]),
]),
list_parser: ListParser::new("^<package>-<version>"),
prompt_arg: PromptArgument::Skip("-y".into()),
prompt_for: vec![CommandType::InstallPackage],
version_arg: VersionArgument::None,
}
}
pub(crate) fn pkgin() -> PackageManagerConfig {
PackageManagerConfig {
commands: HashMap::from_iter([
(
CommandType::InstallPackage,
string_vec!["pkgin", "install", "$"],
),
(CommandType::ListPackages, string_vec!["pkgin", "list"]),
(CommandType::UpdateIndex, string_vec!["pkgin", "update"]),
]),
list_parser: ListParser::new("^<package>-<version>"),
prompt_arg: PromptArgument::Skip("-y".into()),
prompt_for: vec![CommandType::InstallPackage, CommandType::UpdateIndex],
version_arg: VersionArgument::Inline("-".into()),
}
}
pub(crate) fn scoop() -> PackageManagerConfig {
PackageManagerConfig {
commands: HashMap::from_iter([
(
CommandType::InstallPackage,
string_vec!["scoop", "install", "$"],
),
(CommandType::ListPackages, string_vec!["scoop", "list"]),
(CommandType::UpdateIndex, string_vec!["scoop", "update"]),
]),
list_parser: ListParser::new("^<package> <version>"),
prompt_arg: PromptArgument::None,
prompt_for: vec![],
version_arg: VersionArgument::Inline("@".into()),
}
}
pub(crate) fn yum() -> PackageManagerConfig {
PackageManagerConfig {
commands: HashMap::from_iter([
(
CommandType::InstallPackage,
string_vec!["yum", "install", "$"],
),
(
CommandType::ListPackages,
string_vec!["yum", "list", "installed"],
),
(CommandType::UpdateIndex, string_vec!["yum", "check-update"]),
]),
list_parser: ListParser::new("^<package>.<arch>\\s+<version>"),
prompt_arg: PromptArgument::Skip("-y".into()),
prompt_for: vec![CommandType::InstallPackage],
version_arg: VersionArgument::Inline("-".into()),
}
}