Full rewrite #24
102
src/config.rs
102
src/config.rs
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
}
|
54
src/main.rs
54
src/main.rs
|
@ -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 haven’t 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 {
|
||||
|
|
Loading…
Reference in New Issue