use std::fs;
use std::path;
use merge::Merge;
use serde::Deserialize;
use structopt::StructOpt;
use crate::doc;
use crate::viewer;
#[derive(Debug, Default, Deserialize, Merge, StructOpt)]
#[serde(default)]
pub struct Args {
#[merge(skip)]
#[serde(skip)]
pub keyword: doc::Name,
#[merge(strategy = merge::vec::prepend)]
#[structopt(name = "source", short, long, number_of_values = 1)]
pub source_paths: Vec<String>,
#[structopt(long, parse(try_from_str = viewer::get_viewer))]
#[serde(deserialize_with = "deserialize_viewer")]
pub viewer: Option<Box<dyn viewer::Viewer>>,
#[merge(strategy = merge::bool::overwrite_false)]
#[structopt(long)]
pub no_default_sources: bool,
#[merge(strategy = merge::bool::overwrite_false)]
#[structopt(long)]
pub no_search: bool,
#[merge(strategy = merge::bool::overwrite_false)]
#[structopt(short, long)]
pub examples: bool,
#[merge(skip)]
#[structopt(short, long)]
#[serde(skip)]
pub config_file: Option<String>,
#[structopt(flatten)]
#[serde(flatten)]
pub viewer_args: ViewerArgs,
}
#[derive(Debug, Default, Deserialize, Merge, StructOpt)]
#[serde(default)]
pub struct ViewerArgs {
#[merge(strategy = merge::bool::overwrite_false)]
#[structopt(long)]
pub no_syntax_highlight: bool,
#[structopt(long)]
pub theme: Option<String>,
#[structopt(long)]
pub width: Option<usize>,
#[structopt(long)]
pub max_width: Option<usize>,
#[structopt(long)]
pub pager: Option<String>,
}
impl Args {
pub fn load() -> anyhow::Result<Args> {
let mut args = Args::from_args();
if let Some(config) = Args::load_config(args.config_file.as_deref())? {
args.merge(config);
}
Ok(args)
}
fn load_config(file: Option<&str>) -> anyhow::Result<Option<Args>> {
let path = if let Some(file) = file {
if file == "-" {
None
} else {
Some(path::PathBuf::from(file))
}
} else {
let dirs = xdg::BaseDirectories::with_prefix("rusty-man")?;
dirs.find_config_file("config.toml")
};
if let Some(path) = path {
log::info!("Loading configuration file '{}'", path.display());
let s = fs::read_to_string(path)?;
toml::from_str(&s).map(Some).map_err(From::from)
} else {
Ok(None)
}
}
}
fn deserialize_viewer<'de, D>(d: D) -> Result<Option<Box<dyn viewer::Viewer>>, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
let s: Option<&str> = Deserialize::deserialize(d)?;
s.map(|s| viewer::get_viewer(s).map_err(D::Error::custom))
.transpose()
}