Full rewrite #24

Open
emma wants to merge 30 commits from rewrite into main
3 changed files with 112 additions and 91 deletions
Showing only changes of commit 3eac2d2eab - Show all commits

View File

@ -20,7 +20,7 @@
use std::{
fs::File,
io::Read,
io::{Read, self},
path::PathBuf,
};
@ -31,6 +31,8 @@ use yacexits::{
EX_UNAVAILABLE,
};
use crate::error::CError;
#[derive(Deserialize)]
pub struct Config {
pub hopfiles: Vec<String>,
@ -42,56 +44,64 @@ pub struct Sources {
pub modrinth: Vec<String>,
}
pub fn get_config(dirs: BaseDirectories) -> Result<PathBuf, (String, u32)> {
match dirs.place_config_file("config.toml") {
Ok(file) => Ok(file),
Err(_) => {
Err((
format!("Unable to create configuration file."),
EX_UNAVAILABLE,
))
},
}
pub enum ConfigError {
CreateFailed(io::Error),
OpenError(io::Error),
ReadError(io::Error),
FormatError(std::string::FromUtf8Error),
ParseError(toml::de::Error),
}
impl Config {
pub fn read_config(config_path: PathBuf) -> Result<Self, (String, u32)> {
let mut buf: Vec<u8> = Vec::new();
let mut config_file = match File::open(&config_path) {
Ok(file) => file,
Err(_) => {
return Err((
format!("{}: Permission denied.", config_path.display()),
EX_UNAVAILABLE,
));
impl CError for ConfigError {
fn message(&self) -> String {
match self {
Self::CreateFailed(err) => {
format!("Unable to create configuration file: {}", err)
},
};
if let Some(err) = config_file.read_to_end(&mut buf).err() {
return Err((format!("{:?}", err), EX_DATAERR));
};
let toml = match String::from_utf8(buf) {
Ok(contents) => contents,
Err(err) => {
return Err((
format!("{:?}", err),
EX_DATAERR,
));
Self::OpenError(err) => {
format!("Unable to open configuration file: {}", err)
},
};
match toml::from_str(&toml) {
Ok(val) => Ok(val),
Err(_) => {
Err((
format!(
"{}: Invalid configuration file.", config_path.display()
),
EX_DATAERR,
))
Self::ReadError(err) => {
format!("Error while reading configuration file: {}", err)
},
Self::FormatError(err) => {
format!("Configuration file is not valid utf-8: {}", err)
},
Self::ParseError(err) => {
format!("Unable to parse configuration file: {}", err)
},
}
}
fn code(&self) -> u32 {
match self {
Self::CreateFailed(_) => EX_UNAVAILABLE,
Self::OpenError(_) => EX_UNAVAILABLE,
Self::ReadError(_) => EX_DATAERR,
Self::FormatError(_) => EX_DATAERR,
Self::ParseError(_) => EX_DATAERR,
}
}
}
pub fn get_config(dirs: BaseDirectories) -> Result<PathBuf, ConfigError> {
dirs.place_config_file("config.toml").map_err(ConfigError::CreateFailed)
}
impl Config {
pub fn read_config(config_path: PathBuf) -> Result<Self, ConfigError> {
let mut buf: Vec<u8> = Vec::new();
let mut config_file = File::open(&config_path)
.map_err(ConfigError::OpenError)?;
config_file.read_to_end(&mut buf)
.map_err(ConfigError::ReadError)?;
let toml = String::from_utf8(buf)
.map_err(ConfigError::FormatError)?;
toml::from_str(&toml).map_err(ConfigError::ParseError)
}
}

47
src/error.rs Normal file
View File

@ -0,0 +1,47 @@
use yacexits::*;
pub trait CError {
fn code(&self) -> u32;
fn message(&self) -> String;
fn exit(&self) -> ! {
eprintln!("{}: {}", program_invokation(), self.message());
exit(self.code());
}
}
fn program_invokation() -> String {
// TODO: ideally this would be argv[0] from main.
// This could be done with a const OnceCell, but I'm not sure I like that solution.
// Using std, we can do this though:
std::env::args().next()
// with a fallback to the program name
.unwrap_or_else(|| env!("CARGO_PKG_NAME").to_owned())
}
impl<'l> CError for arg::ParseKind<'l> {
fn message(&self) -> String {
format!(
"Usage: {}{}",
program_invokation(), // argv[0],
" [-v] add | get | init | list | remove | update\n\n".to_owned() +
"add [-m version] [-f hopfiles...] packages...\n" +
"get [-n] [-d directory] [-m versions...] [-t types...] packages\n" +
"init [-f hopfiles...] version type\n" +
"list [[-f hopfiles...] | [-m versions...] [-t types...]]\n" +
"remove [[-f hopfiles...] | type version]] packages...\n" +
"update [[-f hopfiles... | [-m versions...] [-t types...]]",
)
}
fn code(&self) -> u32 { EX_USAGE }
}
impl CError for xdg::BaseDirectoriesError {
fn message(&self) -> String {
format!("Unable to open configuration file: {}", self)
}
fn code(&self) -> u32 { EX_UNAVAILABLE }
}

View File

@ -26,18 +26,18 @@ mod args;
mod client;
mod config;
mod hopfile;
mod error;
use api::*;
use args::*;
use client::*;
use config::*;
use hopfile::*;
use error::*;
use yacexits::{
exit,
EX_SOFTWARE,
EX_UNAVAILABLE,
EX_USAGE,
};
struct AppContext {
@ -50,55 +50,19 @@ struct AppContext {
async fn rust_main(arguments: c_main::Args) {
let argv: Vec<&str> = arguments.into_iter().collect();
let usage_info = format!(
"Usage: {}{}",
argv[0],
" [-v] add | get | init | list | remove | update\n\n".to_owned() +
"add [-m version] [-f hopfiles...] packages...\n" +
"get [-n] [-d directory] [-m versions...] [-t types...] packages\n" +
"init [-f hopfiles...] version type\n" +
"list [[-f hopfiles...] | [-m versions...] [-t types...]]\n" +
"remove [[-f hopfiles...] | type version]] packages...\n" +
"update [[-f hopfiles... | [-m versions...] [-t types...]]",
);
let args = Arguments::from_args(
argv
.clone()
.into_iter()
).unwrap_or_else(|_| {
eprintln!("{}", usage_info);
exit(EX_USAGE);
});
).unwrap_or_else(|e| e.exit());
let xdg_basedirs = xdg::BaseDirectories::with_prefix("hopper");
// this might be cursed; I havent decided
let config = match get_config(
xdg_basedirs.unwrap_or_else(|_| {
eprintln!(
"{}: Unable to open configuration file: Permission denied.",
argv[0],
);
exit(EX_UNAVAILABLE);
})
) {
Ok(path) => {
match Config::read_config(path) {
Ok(file) => file,
Err((err, code)) => {
eprintln!("{}: {}", argv[0], err);
exit(code);
},
}
},
Err((err, code)) => {
eprintln!("{}: {}", argv[0], err);
exit(code);
},
};
let xdg_basedirs = xdg::BaseDirectories::with_prefix("hopper")
.unwrap_or_else(|e| e.exit());
let config = get_config(xdg_basedirs)
.and_then(Config::read_config)
.unwrap_or_else(|e| e.exit());
let ctx = AppContext { args, config };
match ctx.args.sub {