use std::fs::File;
use std::io;
use std::path::{PathBuf, Path};
use std::rc::Rc;
use anyhow::anyhow;
use directories::ProjectDirs;
use log::{debug, error};
use serde::{Serialize, Deserialize};
use structopt::StructOpt;
use tempfile::NamedTempFile;
use crate::assets::HIGHLIGHT_JS_VERSION;
#[derive(Debug, StructOpt)]
pub struct Options {
#[structopt(short, long)]
pub debug: bool,
#[structopt(name = "input-file.md", parse(from_os_str))]
pub input_file: Option<PathBuf>,
#[structopt(long = "no-watch", parse(from_flag = std::ops::Not::not))]
pub watch: bool,
#[structopt(long = "output", name = "directory")]
pub output_dir: Option<PathBuf>,
#[structopt(long)]
pub install_default_config: bool,
}
impl Options {
pub fn build() -> Self {
let description = &[
"A simple self-contained markdown previewer. ",
"",
&format!("Code highlighting via highlight.js version {}", HIGHLIGHT_JS_VERSION),
"",
&format!("Edit configuration in: {}", Config::yaml_path().display()),
&format!("Add custom CSS in: {}", Config::css_path().display()),
].join("\n");
let options_app = Options::clap().
long_about(description.as_str());
Options::from_clap(&options_app.get_matches())
}
pub fn init_logging(&self) {
if self.debug {
env_logger::builder().
filter_level(log::LevelFilter::Debug).
init();
} else {
env_logger::builder().
format_module_path(false).
format_timestamp(None).
filter_level(log::LevelFilter::Warn).
init();
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub zoom: f64,
pub editor_command: Vec<String>,
}
impl Default for Config {
fn default() -> Self {
Self { zoom: 1.0, editor_command: Vec::new() }
}
}
impl Config {
pub fn load() -> Option<Self> {
let yaml_path = Self::yaml_path();
let config_file = File::open(&yaml_path).map_err(|_| {
debug!("Didn't find config file: {}", yaml_path.display());
}).ok()?;
serde_yaml::from_reader(&config_file).map_err(|e| {
error!("Couldn't parse YAML config file: {}", e);
}).ok()
}
pub fn yaml_path() -> PathBuf {
ProjectDirs::from("com", "andrewradev", "quickmd").
map(|pd| pd.config_dir().join("config.yaml")).
unwrap_or_else(|| PathBuf::from("./quickmd.yaml"))
}
pub fn css_path() -> PathBuf {
ProjectDirs::from("com", "andrewradev", "quickmd").
map(|pd| pd.config_dir().join("custom.css")).
unwrap_or_else(|| PathBuf::from("./quickmd.css"))
}
pub fn try_install_default() -> anyhow::Result<()> {
let yaml_path = Config::yaml_path();
if yaml_path.exists() {
Err(anyhow!("An existing file was found at: {}\n\
If you want to replace it, please delete it first", yaml_path.display()))
} else {
yaml_path.parent().map(std::fs::create_dir_all);
Ok(std::fs::write(yaml_path, include_str!("../res/default_config.yaml"))?)
}
}
}
#[derive(Debug, Clone)]
pub enum InputFile {
Filesystem(PathBuf),
Stdin(Rc<NamedTempFile>),
}
impl InputFile {
pub fn from(path: &Path, mut contents: impl io::Read) -> anyhow::Result<InputFile> {
if path == PathBuf::from("-") {
let mut tempfile = NamedTempFile::new()?;
io::copy(&mut contents, &mut tempfile)?;
Ok(InputFile::Stdin(Rc::new(tempfile)))
} else {
Ok(InputFile::Filesystem(path.to_path_buf()))
}
}
pub fn path(&self) -> &Path {
match self {
Self::Filesystem(path_buf) => path_buf.as_path(),
Self::Stdin(tempfile) => tempfile.path(),
}
}
pub fn is_real_file(&self) -> bool {
matches!(self, Self::Filesystem(_))
}
}