use std::{borrow, error, fmt, fs, io, ops};
use std::cell::RefCell;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use clap::{Arg, ArgMatches, Command};
use serde::Deserialize;
use toml::Spanned;
use crate::http;
use crate::log::{ExitError, Failed, LogConfig};
use crate::manager::{Manager, TargetSet, UnitSet};
#[derive(Deserialize)]
pub struct Config {
pub units: UnitSet,
pub targets: TargetSet,
#[serde(flatten)]
pub log: LogConfig,
#[serde(flatten)]
pub http: http::Server,
}
impl Config {
pub fn init() -> Result<(), ExitError> {
LogConfig::init_logging()
}
pub fn from_toml(
slice: &[u8], 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_slice(slice);
ConfigPath::clear_base_path();
res
}
pub fn config_args(app: Command) -> Command {
let app = app.arg(
Arg::new("config")
.short('c')
.long("config")
.required(true)
.takes_value(true)
.value_name("PATH")
.help("Read base configuration from this file")
);
LogConfig::config_args(app)
}
pub fn from_arg_matches(
matches: &ArgMatches,
cur_dir: &Path,
manager: &mut Manager,
) -> Result<Self, Failed> {
let conf_path = cur_dir.join(matches.value_of("config").unwrap());
let conf = match ConfigFile::load(&conf_path) {
Ok(conf) => conf,
Err(err) => {
eprintln!(
"Failed to read config file '{}': {}",
conf_path.display(),
err
);
return Err(Failed)
}
};
let mut res = manager.load(conf)?;
res.log.update_with_arg_matches(matches, cur_dir)?;
res.log.switch_logging(false)?;
Ok(res)
}
}
#[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.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: Vec<u8>,
line_starts: Vec<usize>,
}
impl ConfigFile {
pub fn load(path: &impl AsRef<Path>) -> Result<Self, io::Error> {
fs::read(path).map(|bytes| {
ConfigFile {
source: path.into(),
line_starts: bytes.split(|ch| *ch == b'\n').fold(
vec![0], |mut starts, slice| {
starts.push(
starts.last().unwrap() + slice.len()
);
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) -> &[u8] {
&self.bytes
}
fn resolve_pos(&self, pos: usize) -> LineCol {
let line = self.line_starts.iter().find(|&&start|
start < pos
).copied().unwrap_or(self.line_starts.len());
let line = line - 1;
let col = self.line_starts[line] - pos;
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.line_col().map(|(line, col)| {
LineCol { line: line + 1, col: col + 1 }
})
},
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 { }
#[derive(
Clone, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd
)]
#[serde(from = "String")]
pub struct ConfigPath(PathBuf);
impl ConfigPath {
thread_local!(
static BASE_PATH: RefCell<Option<PathBuf>> = RefCell::new(None)
);
fn set_base_path(path: PathBuf) {
Self::BASE_PATH.with(|base_path| {
base_path.replace(Some(path));
})
}
fn clear_base_path() {
Self::BASE_PATH.with(|base_path| {
base_path.replace(None);
})
}
}
impl From<PathBuf> for ConfigPath {
fn from(path: PathBuf) -> Self {
Self(path)
}
}
impl From<ConfigPath> for PathBuf {
fn from(path: ConfigPath) -> Self {
path.0
}
}
impl From<String> for ConfigPath {
fn from(path: String) -> Self {
Self::BASE_PATH.with(|base_path| {
ConfigPath(
match base_path.borrow().as_ref() {
Some(base_path) => base_path.join(path.as_str()),
None => path.into()
}
)
})
}
}
impl ops::Deref for ConfigPath {
type Target = Path;
fn deref(&self) -> &Self::Target {
self.0.as_ref()
}
}
impl AsRef<Path> for ConfigPath {
fn as_ref(&self) -> &Path {
self.0.as_ref()
}
}