use std::{borrow, error, fmt, fs, io, ops};
use std::path::Path;
use std::sync::Arc;
use clap::{Args as _, FromArgMatches};
use daemonbase::logging;
use daemonbase::config::ConfigPath;
use daemonbase::error::Failed;
use serde::Deserialize;
use toml::Spanned;
use crate::http;
use crate::manager::{HttpClientConfig, Manager, TargetSet, UnitSet};
#[derive(Deserialize)]
pub struct Config {
pub units: UnitSet,
pub targets: TargetSet,
#[serde(flatten)]
pub log: logging::Config,
#[serde(flatten)]
pub http: http::Server,
#[serde(flatten)]
pub http_client: HttpClientConfig,
}
impl Config {
pub fn config_args(app: clap::Command) -> clap::Command {
Args::augment_args(app)
}
pub fn from_toml(
slice: &str, base_dir: Option<impl AsRef<Path>>,
) -> Result<Self, toml::de::Error> {
if let Some(ref base_dir) = base_dir {
ConfigPath::set_base_path(base_dir.as_ref().into())
}
let res = toml::de::from_str(slice);
ConfigPath::clear_base_path();
res
}
pub fn from_arg_matches(
matches: &clap::ArgMatches,
) -> Result<(Manager, Self), Failed> {
let args = Args::from_arg_matches(
matches
).expect("bug in command line arguments parser");
let conf = match ConfigFile::load(&args.config) {
Ok(conf) => conf,
Err(err) => {
eprintln!(
"Failed to read config file '{}': {}",
args.config.display(),
err
);
return Err(Failed)
}
};
let (manager, mut config) = Manager::load(conf)?;
config.log.apply_args(&args.log);
Ok((manager, config))
}
}
#[derive(clap::Parser)]
pub struct Args {
#[arg(short, long)]
pub config: ConfigPath,
#[command(flatten)]
pub log: logging::Args,
}
#[derive(Clone, Debug)]
struct Source {
path: Option<Arc<Path>>,
}
impl<'a, T: AsRef<Path>> From<&'a T> for Source {
fn from(path: &'a T) -> Source {
Source {
path: Some(path.as_ref().into())
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
struct LineCol {
pub line: usize,
pub col: usize
}
#[derive(Clone, Debug, Deserialize)]
#[serde(from = "Spanned<T>")]
pub struct Marked<T> {
value: T,
index: usize,
source: Option<Source>,
pos: Option<LineCol>,
}
impl<T> Marked<T> {
pub fn resolve_config(&mut self, config: &ConfigFile) {
self.source = Some(config.source.clone());
self.pos = Some(config.resolve_pos(self.index));
}
pub fn as_inner(&self) -> &T {
&self.value
}
pub fn into_inner(self) -> T {
self.value
}
pub fn mark<U>(&self, value: U) -> Marked<U> {
Marked {
value,
index: self.index,
source: self.source.clone(),
pos: self.pos,
}
}
pub fn format_mark(&self, f: &mut fmt::Formatter) -> fmt::Result {
let source = self.source.as_ref().and_then(|source|
source.path.as_ref()
);
match (source, self.pos) {
(Some(source), Some(pos)) => {
write!(f, "{}:{}:{}", source.display(), pos.line, pos.col)
}
(Some(source), None) => write!(f, "{}", source.display()),
(None, Some(pos)) => write!(f, "{}:{}", pos.line, pos.col),
(None, None) => Ok(())
}
}
}
impl<T> From<T> for Marked<T> {
fn from(src: T) -> Marked<T> {
Marked {
value: src,
index: 0,
source: None, pos: None,
}
}
}
impl<T> From<Spanned<T>> for Marked<T> {
fn from(src: Spanned<T>) -> Marked<T> {
Marked {
index: src.span().start,
value: src.into_inner(),
source: None, pos: None,
}
}
}
impl<T> ops::Deref for Marked<T> {
type Target = T;
fn deref(&self) -> &T {
self.as_inner()
}
}
impl<T> AsRef<T> for Marked<T> {
fn as_ref(&self) -> &T {
self.as_inner()
}
}
impl<T> borrow::Borrow<T> for Marked<T> {
fn borrow(&self) -> &T {
self.as_inner()
}
}
impl<T: fmt::Display> fmt::Display for Marked<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.format_mark(f)?;
write!(f, ": {}", self.value)
}
}
impl<T: error::Error> error::Error for Marked<T> { }
#[derive(Debug)]
pub struct ConfigFile {
source: Source,
bytes: String,
line_starts: Vec<usize>,
}
impl ConfigFile {
pub fn load(path: &impl AsRef<Path>) -> Result<Self, io::Error> {
fs::read_to_string(path).map(|bytes| {
ConfigFile {
source: path.into(),
line_starts: bytes.split('\n').fold(
vec![0], |mut starts, slice| {
starts.push(
starts.last().unwrap() + slice.len() + 1
);
starts
}
),
bytes,
}
})
}
pub fn path(&self) -> Option<&Path> {
self.source.path.as_ref().map(|path| path.as_ref())
}
pub fn dir(&self) -> Option<&Path> {
self.source.path.as_ref().and_then(|path| path.parent())
}
pub fn bytes(&self) -> &str {
&self.bytes
}
fn resolve_pos(&self, pos: usize) -> LineCol {
let line = self.line_starts.iter().enumerate().find_map(|(i, start)|
if *start > pos {
Some(i)
}
else {
None
}
).unwrap_or(self.line_starts.len());
let line = line - 1;
let col = pos - self.line_starts[line];
LineCol { line, col }
}
}
#[derive(Clone, Debug)]
pub struct ConfigError {
err: toml::de::Error,
pos: Marked<()>,
}
impl ConfigError {
pub fn new(err: toml::de::Error, file: &ConfigFile) -> Self {
ConfigError {
pos: Marked {
value: (),
index: 0,
source: Some(file.source.clone()),
pos: err.span().map(|range| {
file.resolve_pos(range.start)
}),
},
err,
}
}
}
impl fmt::Display for ConfigError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.pos.format_mark(f)?;
write!(f, ": {}", self.err)
}
}
impl error::Error for ConfigError { }