Overall cleanup, refactor and performance tweaks
This commit is contained in:
parent
7d89f51eac
commit
10d47dc51c
11 changed files with 280 additions and 222 deletions
|
|
@ -82,6 +82,7 @@ match_wild_err_arm = "warn"
|
||||||
match_wildcard_for_single_variants = "warn"
|
match_wildcard_for_single_variants = "warn"
|
||||||
maybe_infinite_iter = "warn"
|
maybe_infinite_iter = "warn"
|
||||||
mismatching_type_param_order = "warn"
|
mismatching_type_param_order = "warn"
|
||||||
|
misnamed_getters = "warn"
|
||||||
missing_assert_message = "warn"
|
missing_assert_message = "warn"
|
||||||
missing_const_for_fn = "warn"
|
missing_const_for_fn = "warn"
|
||||||
missing_fields_in_debug = "warn"
|
missing_fields_in_debug = "warn"
|
||||||
|
|
|
||||||
212
src/conf.rs
212
src/conf.rs
|
|
@ -9,66 +9,70 @@ use crate::{
|
||||||
run::Command,
|
run::Command,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn load() -> Configuration {
|
pub fn load() -> Result<Configuration, Error> {
|
||||||
log::elog("Loading configuration");
|
log::elog("Loading configuration");
|
||||||
|
|
||||||
let mut conf = Configuration {
|
let mut candidate = Configuration::default();
|
||||||
su_command: SuCommand {
|
|
||||||
command: Command::new("su", &["-c", "{% command %}"]),
|
|
||||||
wraps: true,
|
|
||||||
},
|
|
||||||
su_command_wraps: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let root = get_root();
|
let root = get_root();
|
||||||
let Ok(contents) = fs::read_to_string(root.join("tori.conf")) else {
|
let contents = fs::read_to_string(root.join("tori.conf"))?;
|
||||||
eprintln!("Failed reading configuration file at {root:?}");
|
|
||||||
return conf;
|
|
||||||
};
|
|
||||||
|
|
||||||
let lines: Vec<(&str, &str)> = contents
|
let map: HashMap<String, String> = contents
|
||||||
.lines()
|
.lines()
|
||||||
.filter_map(|line| line.split_once('='))
|
.filter_map(|line| line.split_once('='))
|
||||||
|
.map(|(k, v)| (k.to_owned(), v.to_owned()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut map: HashMap<String, String> = HashMap::new();
|
if let Some(su_command) = map.get("su_command") {
|
||||||
|
let wraps = map.get("su_command_wraps").is_some_and(|v| v == "true");
|
||||||
for line in &lines {
|
candidate.su_command = parse_su_command(su_command, wraps)?;
|
||||||
let (key, value) = line;
|
|
||||||
map.insert(key.to_string(), value.to_string());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
elog(&format!("{lines:#?}"));
|
if let Some(merge_strategy) = map.get("merge_strategy") {
|
||||||
|
candidate.merge_strategy = match merge_strategy.as_str() {
|
||||||
if let Some(su_command) = map.get("su_command") {
|
"prefer configuration" => MergeStrategy::PreferConfig,
|
||||||
let split: Vec<String> = su_command
|
"prefer system" => MergeStrategy::PreferSystem,
|
||||||
.split(' ')
|
_ => MergeStrategy::default(),
|
||||||
.filter(|s| !s.is_empty())
|
|
||||||
.map(|s| s.to_string())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if let Some((base, args)) = split.split_first()
|
|
||||||
&& let Ok(resolved_path) = resolve_from_path(base)
|
|
||||||
{
|
|
||||||
elog(&format!(
|
|
||||||
"Succesfully resolved 'su_command' configuration value \
|
|
||||||
{su_command} through PATH to {resolved_path:?}, with base \
|
|
||||||
{base} and args {args:?}"
|
|
||||||
));
|
|
||||||
conf.su_command = SuCommand {
|
|
||||||
command: Command::new_from_strings(base, args),
|
|
||||||
wraps: map.get("su_command_wraps").is_some_and(|v| v == "true"),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
eprintln!("Failed validation of 'su_command' configuration value");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(su_command_wraps) = map.get("su_command_wraps") {
|
Ok(candidate)
|
||||||
conf.su_command_wraps = Some(su_command_wraps == "true");
|
}
|
||||||
}
|
|
||||||
|
|
||||||
conf
|
fn parse_su_command(config_value: &str, wraps: bool) -> Result<SuCommand, Error> {
|
||||||
|
let split: Vec<&str> = config_value.split(' ').filter(|s| !s.is_empty()).collect();
|
||||||
|
|
||||||
|
let Some((base, args)) = split.split_first() else {
|
||||||
|
return Err(Error::new(
|
||||||
|
"Configuration line is empty",
|
||||||
|
ErrorKind::MalformedConfigLine,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(resolved_base) = resolve_command(base) else {
|
||||||
|
return Err(Error::new(
|
||||||
|
"su_command does not resolve to a command in PATH",
|
||||||
|
ErrorKind::CommandNotInPath,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(resolved_base_str) = resolved_base.to_str() else {
|
||||||
|
return Err(Error::new(
|
||||||
|
"su_command path contains invalid characters (expected UTF-8)",
|
||||||
|
ErrorKind::UTF8,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
elog(&format!(
|
||||||
|
"Successfully resolved 'su_command' configuration value \
|
||||||
|
{config_value} through PATH from base {base} and args {args:?} \
|
||||||
|
to {resolved_base:?}"
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(SuCommand {
|
||||||
|
command: Command::new(resolved_base_str, args),
|
||||||
|
wraps,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_root() -> PathBuf {
|
fn get_root() -> PathBuf {
|
||||||
|
|
@ -93,26 +97,24 @@ fn get_root() -> PathBuf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_from_path(command: &str) -> Result<PathBuf, String> {
|
fn resolve_command(command: &str) -> Result<PathBuf, Error> {
|
||||||
elog(&format!("Solving from PATH for {command}"));
|
elog(&format!("Solving from PATH for {command}"));
|
||||||
|
|
||||||
let paths: Vec<PathBuf> = if let Ok(path) = std::env::var("PATH") {
|
let path_var = std::env::var("PATH")?;
|
||||||
path.split(':')
|
|
||||||
.filter(|p| !p.is_empty() && PathBuf::from(p).is_dir())
|
let paths = path_var
|
||||||
.map(PathBuf::from)
|
.split(':')
|
||||||
.collect()
|
.filter(|p| !p.is_empty() && PathBuf::from(p).is_dir())
|
||||||
} else {
|
.map(PathBuf::from);
|
||||||
elog("Error: PATH is not set");
|
|
||||||
return Err(format!(
|
|
||||||
"{command} not found: PATH is not set in the environment"
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
elog(&format!("Gathered paths {paths:?}"));
|
elog(&format!("Gathered paths {paths:?}"));
|
||||||
|
|
||||||
for path in paths {
|
for path in paths {
|
||||||
elog(&format!("On path {path:?}"));
|
let Ok(mut entries) = fs::read_dir(&path) else {
|
||||||
let Ok(mut entries) = fs::read_dir(path) else {
|
elog(&format!(
|
||||||
elog("Skipping: Could not read directory contents");
|
"Skipping: Could not read directory contents for path {:?}",
|
||||||
|
&path
|
||||||
|
));
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -136,29 +138,107 @@ fn resolve_from_path(command: &str) -> Result<PathBuf, String> {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Err(format!(
|
Err(Error {
|
||||||
"{command} not found in any of the directories in PATH"
|
message: format!("{command} not found in any of the directories in PATH"),
|
||||||
))
|
kind: ErrorKind::CommandNotInPath,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Configuration {
|
pub struct Configuration {
|
||||||
pub su_command: SuCommand,
|
pub su_command: SuCommand,
|
||||||
pub su_command_wraps: Option<bool>,
|
pub merge_strategy: MergeStrategy,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum MergeStrategy {
|
||||||
|
PreferSystem,
|
||||||
|
PreferConfig,
|
||||||
|
#[default]
|
||||||
|
Interactive,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct SuCommand {
|
pub struct SuCommand {
|
||||||
command: Command,
|
command: Command,
|
||||||
wraps: bool,
|
wraps: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SuCommand {
|
impl SuCommand {
|
||||||
pub fn command(&self) -> Command {
|
pub const fn command(&self) -> &Command {
|
||||||
self.command.clone()
|
&self.command
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn wraps(&self) -> bool {
|
pub const fn wraps(&self) -> bool {
|
||||||
self.wraps
|
self.wraps
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for SuCommand {
|
||||||
|
fn default() -> SuCommand {
|
||||||
|
SuCommand {
|
||||||
|
command: Command::new("su", &["-c", "{% command %}"]),
|
||||||
|
wraps: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Error {
|
||||||
|
message: String,
|
||||||
|
kind: ErrorKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
pub fn new(message: &str, kind: ErrorKind) -> Error {
|
||||||
|
Error {
|
||||||
|
message: message.to_owned(),
|
||||||
|
kind,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "{}: {}", self.kind, self.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::env::VarError> for Error {
|
||||||
|
fn from(var_error: std::env::VarError) -> Error {
|
||||||
|
Error {
|
||||||
|
message: format!("Environment variable error: {var_error}"),
|
||||||
|
kind: ErrorKind::VarError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for Error {
|
||||||
|
fn from(io_error: std::io::Error) -> Error {
|
||||||
|
Error {
|
||||||
|
message: format!("{}: {io_error}", io_error.kind()),
|
||||||
|
kind: ErrorKind::IO,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ErrorKind {
|
||||||
|
CommandNotInPath,
|
||||||
|
VarError,
|
||||||
|
MalformedConfigLine,
|
||||||
|
UTF8,
|
||||||
|
IO,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ErrorKind {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
use ErrorKind::*;
|
||||||
|
let s = match self {
|
||||||
|
VarError => "Environment variable error",
|
||||||
|
CommandNotInPath => "Command not in PATH",
|
||||||
|
MalformedConfigLine => "Malformed configuration line",
|
||||||
|
UTF8 => "Invalid characters could not be decoded (expected UTF-8)",
|
||||||
|
IO => "Input/Output error",
|
||||||
|
};
|
||||||
|
write!(f, "{s}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
pub fn elog(message: &str) {
|
pub fn elog(message: &str) {
|
||||||
// DONE MUST be printed only if DEBUG is set in the environment
|
|
||||||
if let Ok(debug) = std::env::var("DEBUG")
|
if let Ok(debug) = std::env::var("DEBUG")
|
||||||
&& !debug.is_empty()
|
&& !debug.is_empty()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
12
src/main.rs
12
src/main.rs
|
|
@ -2,11 +2,17 @@ use tori::{conf, log, run, state};
|
||||||
|
|
||||||
fn main() -> std::process::ExitCode {
|
fn main() -> std::process::ExitCode {
|
||||||
log::elog(&format!("tori {}", env!("CARGO_PKG_VERSION")));
|
log::elog(&format!("tori {}", env!("CARGO_PKG_VERSION")));
|
||||||
let configuration = conf::load();
|
let configuration = match conf::load() {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(error) => {
|
||||||
|
eprintln!("Configuration error: {error}");
|
||||||
|
return 1.into();
|
||||||
|
}
|
||||||
|
};
|
||||||
log::elog(&format!("Configuration: {configuration:#?}"));
|
log::elog(&format!("Configuration: {configuration:#?}"));
|
||||||
let order = run::teller::parse(std::env::args(), &configuration);
|
let order = run::teller::parse(std::env::args());
|
||||||
log::elog(&format!("Order: {order:#?}"));
|
log::elog(&format!("Order: {order:#?}"));
|
||||||
let state = state::setup(&configuration, &[order]);
|
let state = state::setup(configuration, &[order]);
|
||||||
log::elog(&format!("State: {state:#?}"));
|
log::elog(&format!("State: {state:#?}"));
|
||||||
let result = run::expeditor::fulfill(&state);
|
let result = run::expeditor::fulfill(&state);
|
||||||
log::elog(&format!("Filled Order: {result:#?}"));
|
log::elog(&format!("Filled Order: {result:#?}"));
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,12 @@ pub struct OperatingSystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OperatingSystem {
|
impl OperatingSystem {
|
||||||
pub fn kind(&self) -> Kind {
|
pub const fn kind(&self) -> &Kind {
|
||||||
self.kind.clone()
|
&self.kind
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn packager(&self) -> Packager {
|
pub const fn packager(&self) -> &Packager {
|
||||||
self.packager.clone()
|
&self.packager
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn new(kind: Kind, packager: Packager) -> OperatingSystem {
|
pub const fn new(kind: Kind, packager: Packager) -> OperatingSystem {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::fs::read_to_string;
|
use std::{collections::HashSet, fs::read_to_string, iter};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
conf::Configuration,
|
conf::Configuration,
|
||||||
|
|
@ -38,59 +38,53 @@ impl Packages for Apt {
|
||||||
"dpkg-query",
|
"dpkg-query",
|
||||||
&["--show", "--showformat", "${Package} ${Status}\\n"],
|
&["--show", "--showformat", "${Package} ${Status}\\n"],
|
||||||
))?;
|
))?;
|
||||||
let all: Vec<String> = raw_all
|
let all = raw_all.lines().filter_map(|line| {
|
||||||
.lines()
|
let pair = line.split_once(' ');
|
||||||
.filter_map(|line| {
|
match pair {
|
||||||
let pair = line.split_once(' ');
|
Some((pkg, "install ok installed")) => Some(pkg.to_string()),
|
||||||
match pair {
|
Some(_) => None,
|
||||||
Some((pkg, "install ok installed")) => Some(pkg.to_string()),
|
None => {
|
||||||
Some(_) => None,
|
elog("Warning: Dropped a None pair when cleaning up package list");
|
||||||
None => {
|
None
|
||||||
elog("Warning: Dropped a None pair when cleaning up package list");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let auto_set: HashSet<String> = self
|
||||||
|
.automatic()?
|
||||||
|
.into_iter()
|
||||||
|
.map(|package| package.name().to_owned())
|
||||||
|
.collect();
|
||||||
|
let mut manual_packages: Vec<Package> = all
|
||||||
|
.into_iter()
|
||||||
|
.filter(|name| !auto_set.contains(name))
|
||||||
|
.map(|name| Package::new_with_manual(&name, true))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let auto_packages: Vec<Package> = self.automatic()?;
|
manual_packages.sort();
|
||||||
let mut manual_packages: Vec<Package> = vec![];
|
|
||||||
|
|
||||||
for package in all {
|
|
||||||
let auto = Package::new_with_manual(&package, false);
|
|
||||||
if !auto_packages.contains(&auto) {
|
|
||||||
manual_packages.push(Package::new_with_manual(&package, true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(manual_packages)
|
Ok(manual_packages)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn automatic(&self) -> Result<Vec<Package>, pkg::Error> {
|
fn automatic(&self) -> Result<Vec<Package>, pkg::Error> {
|
||||||
let path = "/var/lib/apt/extended_states";
|
let path = "/var/lib/apt/extended_states";
|
||||||
let Ok(extended_states) = read_to_string(path) else {
|
let extended_states = read_to_string(path)?;
|
||||||
return pkg::Error::send(
|
|
||||||
&format!("Failed reading {path}"),
|
let lines: Vec<&str> = extended_states
|
||||||
pkg::ErrorKind::MetadataFileRead,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
let lines: Vec<String> = extended_states
|
|
||||||
.lines()
|
.lines()
|
||||||
.map(|s| s.to_string())
|
|
||||||
.filter(|line| !line.is_empty())
|
.filter(|line| !line.is_empty())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let iterator = lines.chunks_exact(3);
|
let chunks = lines.chunks_exact(3);
|
||||||
let remainder = iterator.remainder();
|
if !chunks.remainder().is_empty() {
|
||||||
if !remainder.is_empty() {
|
|
||||||
elog(&format!(
|
elog(&format!(
|
||||||
"Warning: Reading package extended states left a remainder: {remainder:?}"
|
"Warning: Package extended states read left a remainder: {:?}",
|
||||||
|
chunks.remainder()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut packages: Vec<Package> = vec![];
|
let mut packages: Vec<Package> = vec![];
|
||||||
|
|
||||||
for chunk in iterator {
|
for chunk in chunks {
|
||||||
if let Some(name_line) = chunk.first()
|
if let Some(name_line) = chunk.first()
|
||||||
&& let Some(auto_line) = chunk.get(2)
|
&& let Some(auto_line) = chunk.get(2)
|
||||||
{
|
{
|
||||||
|
|
@ -140,11 +134,12 @@ impl Packages for Apt {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packages.sort();
|
||||||
Ok(packages)
|
Ok(packages)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn variant(&self) -> Result<PackagerVariant, pkg::Error> {
|
fn variant(&self) -> &PackagerVariant {
|
||||||
Ok(self.variant.clone())
|
&self.variant
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,11 +154,11 @@ impl Apt {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut args = vec![subcommand];
|
let args: Vec<&str> = iter::once(subcommand)
|
||||||
args.extend_from_slice(&packages.iter().map(|p| p.into()).collect::<Vec<&str>>());
|
.chain(packages.iter().map(|p| p.into()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
let command = Command::new("apt", &args).escalate(config)?;
|
let command = Command::new("apt", &args).escalate(config)?;
|
||||||
|
|
||||||
Ok(crate::run::executor::spawn(&command)?)
|
Ok(crate::run::executor::spawn(&command)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ pub trait Packages: Clone + Default + Debug + PartialEq + Eq {
|
||||||
fn uninstall(&self, packages: &[Package], config: &Configuration) -> Result<(), Error>;
|
fn uninstall(&self, packages: &[Package], config: &Configuration) -> Result<(), Error>;
|
||||||
fn manual(&self) -> Result<Vec<Package>, Error>;
|
fn manual(&self) -> Result<Vec<Package>, Error>;
|
||||||
fn automatic(&self) -> Result<Vec<Package>, Error>;
|
fn automatic(&self) -> Result<Vec<Package>, Error>;
|
||||||
fn variant(&self) -> Result<PackagerVariant, Error>;
|
fn variant(&self) -> &PackagerVariant;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug, PartialEq, Eq)]
|
#[derive(Clone, Default, Debug, PartialEq, Eq)]
|
||||||
|
|
@ -51,12 +51,10 @@ impl Packages for Packager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn variant(&self) -> Result<PackagerVariant, Error> {
|
fn variant(&self) -> &PackagerVariant {
|
||||||
match self {
|
match self {
|
||||||
Packager::Apt(p) => p.variant(),
|
Packager::Apt(p) => p.variant(),
|
||||||
Packager::Unknown => Error::unknown_packager(
|
Packager::Unknown => &PackagerVariant::Unknown,
|
||||||
"Can't determine the package manager's variant because it is unknown",
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -68,27 +66,53 @@ pub enum PackagerVariant {
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Eq, PartialEq)]
|
#[derive(Default, Debug, Clone, Eq)]
|
||||||
pub struct Package {
|
pub struct Package {
|
||||||
name: String,
|
name: String,
|
||||||
version: Option<Version>,
|
|
||||||
manual: Option<bool>,
|
manual: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Package {
|
impl Package {
|
||||||
pub fn name(&self) -> String {
|
pub fn name(&self) -> &str {
|
||||||
self.name.clone()
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn manual(&self) -> &Option<bool> {
|
||||||
|
&self.manual
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_with_manual(name: &str, manual: bool) -> Package {
|
pub fn new_with_manual(name: &str, manual: bool) -> Package {
|
||||||
Package {
|
Package {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
version: None,
|
|
||||||
manual: Some(manual),
|
manual: Some(manual),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Package {
|
||||||
|
fn eq(&self, other: &Package) -> bool {
|
||||||
|
self.name() == other.name()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::hash::Hash for Package {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.name().hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for Package {
|
||||||
|
fn partial_cmp(&self, other: &Package) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for Package {
|
||||||
|
fn cmp(&self, other: &Package) -> std::cmp::Ordering {
|
||||||
|
self.name().cmp(other.name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<&String> for Package {
|
impl From<&String> for Package {
|
||||||
fn from(s: &String) -> Package {
|
fn from(s: &String) -> Package {
|
||||||
Package {
|
Package {
|
||||||
|
|
@ -136,38 +160,7 @@ impl<'s> From<&'s Package> for &'s str {
|
||||||
|
|
||||||
impl std::fmt::Display for Package {
|
impl std::fmt::Display for Package {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
if let Some(version) = &self.version {
|
write!(f, "{}", &self.name)
|
||||||
write!(f, "{} {}", &self.name, version)
|
|
||||||
} else {
|
|
||||||
write!(f, "{}", &self.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Eq, PartialEq)]
|
|
||||||
struct Version {
|
|
||||||
major: u32,
|
|
||||||
minor: Option<u32>,
|
|
||||||
patch: Option<u32>,
|
|
||||||
qualifier: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for Version {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
if let Some(minor) = &self.minor
|
|
||||||
&& let Some(patch) = &self.patch
|
|
||||||
&& let Some(qualifier) = &self.qualifier
|
|
||||||
{
|
|
||||||
write!(f, "{}.{minor}.{patch}-{qualifier}", &self.major)
|
|
||||||
} else if let Some(minor) = &self.minor
|
|
||||||
&& let Some(patch) = &self.patch
|
|
||||||
{
|
|
||||||
write!(f, "{}.{minor}.{patch}", &self.major)
|
|
||||||
} else if let Some(minor) = &self.minor {
|
|
||||||
write!(f, "{}.{minor}", &self.major)
|
|
||||||
} else {
|
|
||||||
write!(f, "{}", &self.major)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -185,7 +178,7 @@ impl Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send<T>(message: &str, kind: ErrorKind) -> Result<T, Error> {
|
pub fn wrapped<T>(message: &str, kind: ErrorKind) -> Result<T, Error> {
|
||||||
Err(Error::new(message, kind))
|
Err(Error::new(message, kind))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,6 +208,15 @@ impl From<run::Error> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for Error {
|
||||||
|
fn from(io_error: std::io::Error) -> Error {
|
||||||
|
Error {
|
||||||
|
message: format!("{:?}: {}", io_error.kind(), io_error),
|
||||||
|
kind: ErrorKind::IO,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ErrorKind {
|
pub enum ErrorKind {
|
||||||
NotFound,
|
NotFound,
|
||||||
|
|
@ -222,4 +224,5 @@ pub enum ErrorKind {
|
||||||
MetadataFileRead,
|
MetadataFileRead,
|
||||||
RunError,
|
RunError,
|
||||||
ExecutorError,
|
ExecutorError,
|
||||||
|
IO,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
40
src/run.rs
40
src/run.rs
|
|
@ -18,8 +18,8 @@ impl Order {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tasks(&self) -> Vec<Task> {
|
pub const fn tasks(&self) -> &Vec<Task> {
|
||||||
self.tasks.clone()
|
&self.tasks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,43 +29,32 @@ pub struct Task {
|
||||||
done: bool,
|
done: bool,
|
||||||
argument: String,
|
argument: String,
|
||||||
parameters: Vec<String>,
|
parameters: Vec<String>,
|
||||||
configuration: Configuration,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Task {
|
impl Task {
|
||||||
fn new(
|
fn new(kind: TaskKind, argument: &str, parameters: Vec<String>) -> Task {
|
||||||
kind: TaskKind,
|
|
||||||
argument: &str,
|
|
||||||
parameters: Vec<String>,
|
|
||||||
configuration: &Configuration,
|
|
||||||
) -> Task {
|
|
||||||
Task {
|
Task {
|
||||||
kind,
|
kind,
|
||||||
done: false,
|
done: false,
|
||||||
argument: String::from(argument),
|
argument: String::from(argument),
|
||||||
parameters,
|
parameters,
|
||||||
configuration: configuration.clone(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn configuration(&self) -> Configuration {
|
pub fn argument(&self) -> &str {
|
||||||
self.configuration.clone()
|
&self.argument
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn argument(&self) -> String {
|
pub const fn parameters(&self) -> &Vec<String> {
|
||||||
self.argument.clone()
|
&self.parameters
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parameters(&self) -> Vec<String> {
|
|
||||||
self.parameters.clone()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn done(&self) -> bool {
|
pub const fn done(&self) -> bool {
|
||||||
self.done
|
self.done
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn kind(&self) -> TaskKind {
|
pub const fn kind(&self) -> &TaskKind {
|
||||||
self.kind.clone()
|
&self.kind
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,7 +78,7 @@ pub struct Command {
|
||||||
|
|
||||||
impl Command {
|
impl Command {
|
||||||
pub fn escalate(&self, config: &Configuration) -> Result<Command, Error> {
|
pub fn escalate(&self, config: &Configuration) -> Result<Command, Error> {
|
||||||
let mut args = config.su_command.command().args;
|
let mut args = config.su_command.command().clone().args;
|
||||||
|
|
||||||
if config.su_command.wraps() {
|
if config.su_command.wraps() {
|
||||||
let flattened_command = format!("{} {}", self.base, self.args.join(" "));
|
let flattened_command = format!("{} {}", self.base, self.args.join(" "));
|
||||||
|
|
@ -114,7 +103,7 @@ impl Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Command {
|
Ok(Command {
|
||||||
base: config.su_command.command().base,
|
base: config.su_command.command().clone().base,
|
||||||
args,
|
args,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -125,13 +114,6 @@ impl Command {
|
||||||
args: args.iter().map(|e| e.to_string()).collect(),
|
args: args.iter().map(|e| e.to_string()).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_from_strings(base: &str, args: &[String]) -> Command {
|
|
||||||
Command {
|
|
||||||
base: base.to_string(),
|
|
||||||
args: args.to_vec(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,14 @@ pub fn fulfill(state: &State) -> Result<(), Error> {
|
||||||
state
|
state
|
||||||
.os()
|
.os()
|
||||||
.packager()
|
.packager()
|
||||||
.install(&packages, &state.configuration())?;
|
.install(&packages, state.configuration())?;
|
||||||
}
|
}
|
||||||
TaskKind::PackageUninstall => {
|
TaskKind::PackageUninstall => {
|
||||||
let packages: Vec<Package> = task.parameters.iter().map(|s| s.into()).collect();
|
let packages: Vec<Package> = task.parameters.iter().map(|s| s.into()).collect();
|
||||||
state
|
state
|
||||||
.os()
|
.os()
|
||||||
.packager()
|
.packager()
|
||||||
.uninstall(&packages, &state.configuration())?;
|
.uninstall(&packages, state.configuration())?;
|
||||||
}
|
}
|
||||||
TaskKind::PackageListAuto => {
|
TaskKind::PackageListAuto => {
|
||||||
match state.os().packager().automatic() {
|
match state.os().packager().automatic() {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
conf::Configuration,
|
|
||||||
log::elog,
|
log::elog,
|
||||||
run::{Order, Task, TaskKind},
|
run::{Order, Task, TaskKind},
|
||||||
};
|
};
|
||||||
use std::{env, path::PathBuf};
|
use std::{env, path::PathBuf};
|
||||||
|
|
||||||
pub fn parse(mut raw_args: env::Args, configuration: &Configuration) -> Order {
|
pub fn parse(mut raw_args: env::Args) -> Order {
|
||||||
let (argument, parameters): (String, Vec<String>) = if let Some(first) = raw_args.next() {
|
let (argument, parameters): (String, Vec<String>) = if let Some(first) = raw_args.next() {
|
||||||
if is_executable_path(&first) {
|
if is_executable_path(&first) {
|
||||||
elog("First argument is the executable path");
|
elog("First argument is the executable path");
|
||||||
|
|
@ -32,25 +31,20 @@ pub fn parse(mut raw_args: env::Args, configuration: &Configuration) -> Order {
|
||||||
|
|
||||||
let make_order = |kind: TaskKind| -> Order {
|
let make_order = |kind: TaskKind| -> Order {
|
||||||
Order {
|
Order {
|
||||||
tasks: vec![Task::new(kind, &argument, parameters, configuration)],
|
tasks: vec![Task::new(kind, &argument, parameters)],
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
elog(&format!("Command is {argument}"));
|
elog(&format!("Command is {argument}"));
|
||||||
if argument == "version" || argument == "-v" || argument == "--version" {
|
|
||||||
make_order(TaskKind::Version)
|
match argument.as_str() {
|
||||||
} else if argument == "help" || argument == "-h" || argument == "--help" {
|
"version" | "-v" | "--version" => make_order(TaskKind::Version),
|
||||||
make_order(TaskKind::Help)
|
"help" | "-h" | "--help" => make_order(TaskKind::Help),
|
||||||
} else if argument == "install" {
|
"install" => make_order(TaskKind::PackageInstall),
|
||||||
make_order(TaskKind::PackageInstall)
|
"uninstall" => make_order(TaskKind::PackageUninstall),
|
||||||
} else if argument == "uninstall" {
|
"auto" => make_order(TaskKind::PackageListAuto),
|
||||||
make_order(TaskKind::PackageUninstall)
|
"manual" => make_order(TaskKind::PackageListManual),
|
||||||
} else if argument == "auto" {
|
_ => make_order(TaskKind::Unrecognized),
|
||||||
make_order(TaskKind::PackageListAuto)
|
|
||||||
} else if argument == "manual" {
|
|
||||||
make_order(TaskKind::PackageListManual)
|
|
||||||
} else {
|
|
||||||
make_order(TaskKind::Unrecognized)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
30
src/state.rs
30
src/state.rs
|
|
@ -15,29 +15,29 @@ pub struct State {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
fn new(config: &Configuration, os: &OperatingSystem, orders: &[Order]) -> State {
|
fn new(configuration: Configuration, os: OperatingSystem, orders: &[Order]) -> State {
|
||||||
State {
|
State {
|
||||||
configuration: config.clone(),
|
|
||||||
os: os.clone(),
|
|
||||||
orders: orders.to_vec(),
|
orders: orders.to_vec(),
|
||||||
|
configuration,
|
||||||
|
os,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn configuration(&self) -> Configuration {
|
pub const fn configuration(&self) -> &Configuration {
|
||||||
self.configuration.clone()
|
&self.configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn os(&self) -> OperatingSystem {
|
pub const fn os(&self) -> &OperatingSystem {
|
||||||
self.os.clone()
|
&self.os
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn orders(&self) -> Vec<Order> {
|
pub const fn orders(&self) -> &Vec<Order> {
|
||||||
self.orders.clone()
|
&self.orders
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup(config: &Configuration, orders: &[Order]) -> State {
|
pub fn setup(config: Configuration, orders: &[Order]) -> State {
|
||||||
State::new(config, &detect_os(), orders)
|
State::new(config, detect_os(), orders)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn detect_os() -> OperatingSystem {
|
fn detect_os() -> OperatingSystem {
|
||||||
|
|
@ -46,11 +46,9 @@ fn detect_os() -> OperatingSystem {
|
||||||
if let Ok(os_release) = std::fs::read_to_string("/etc/os-release") {
|
if let Ok(os_release) = std::fs::read_to_string("/etc/os-release") {
|
||||||
elog(&os_release);
|
elog(&os_release);
|
||||||
let mut map: HashMap<String, String> = HashMap::new();
|
let mut map: HashMap<String, String> = HashMap::new();
|
||||||
let lines: Vec<Option<(&str, &str)>> = os_release
|
let lines = os_release.lines().map(|line| line.split_once('='));
|
||||||
.lines()
|
|
||||||
.map(|line| line.split_once('='))
|
for line in lines.flatten() {
|
||||||
.collect();
|
|
||||||
for line in lines.into_iter().flatten() {
|
|
||||||
let (key, value) = line;
|
let (key, value) = line;
|
||||||
map.insert(key.to_string(), strip_quotes(value));
|
map.insert(key.to_string(), strip_quotes(value));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue