use super::utils::{deserialize_absolute_path, make_path_relative};
use derive_more::{AsMut, AsRef, Deref, DerefMut};
use serde::{Deserialize, Serialize};
use std::{
convert::TryFrom,
path::{Component, Path, PathBuf},
};
use uriparse::URI;
#[derive(Copy, Clone, Debug, PartialEq, Eq, AsRef, AsMut, Deref, DerefMut)]
pub struct Indexed<T> {
index: usize,
#[as_ref]
#[as_mut]
#[deref]
#[deref_mut]
inner: T,
}
impl<T> Indexed<T> {
pub fn index(&self) -> usize {
self.index
}
pub fn into_inner(self) -> T {
self.inner
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct HtmlConfig {
#[serde(skip)]
pub runtime: HtmlRuntimeConfig,
#[serde(default)]
pub wikis: Vec<HtmlWikiConfig>,
#[serde(default)]
pub list: HtmlListConfig,
#[serde(default)]
pub paragraph: HtmlParagraphConfig,
#[serde(default)]
pub link: HtmlLinkConfig,
#[serde(default)]
pub header: HtmlHeaderConfig,
#[serde(default)]
pub code: HtmlCodeConfig,
#[serde(default)]
pub comment: HtmlCommentConfig,
#[serde(default)]
pub template: HtmlTemplateConfig,
}
impl HtmlConfig {
pub fn to_active_page_path_to_wiki_root(&self) -> PathBuf {
self.find_active_wiki()
.and_then(|wiki| wiki.path_to_root(self.active_page()))
.unwrap_or_else(PathBuf::new)
}
pub fn as_active_page_path_within_wiki(&self) -> &Path {
self.to_current_wiki()
.path_within(self.active_page())
.expect("Impossible: matched wiki does not contain page")
}
pub fn to_current_wiki(&self) -> HtmlWikiConfig {
self.find_active_wiki()
.cloned()
.unwrap_or_else(|| self.runtime.to_tmp_wiki())
}
pub fn active_page(&self) -> &Path {
self.runtime.page.as_path()
}
pub fn find_active_wiki(&self) -> Option<&HtmlWikiConfig> {
self.runtime
.wiki_index
.as_ref()
.copied()
.and_then(|idx| self.find_wiki_by_index(idx))
}
pub fn find_wiki_by_index(&self, idx: usize) -> Option<&HtmlWikiConfig> {
self.wikis.get(idx)
}
pub fn find_wiki_index_by_name<S: AsRef<str>>(
&self,
name: S,
) -> Option<usize> {
let name = name.as_ref();
self.wikis.iter().enumerate().find_map(|(idx, wiki)| {
if wiki.name.as_deref() == Some(name) {
Some(idx)
} else {
None
}
})
}
pub fn find_wiki_by_name<S: AsRef<str>>(
&self,
name: S,
) -> Option<&HtmlWikiConfig> {
self.find_wiki_index_by_name(name)
.and_then(|idx| self.find_wiki_by_index(idx))
}
pub fn find_wiki_index_by_path<P: AsRef<Path>>(
&self,
path: P,
) -> Option<usize> {
self.wikis
.iter()
.enumerate()
.filter(|(_, wiki)| path.as_ref().starts_with(wiki.path.as_path()))
.max_by_key(|(_, wiki)| wiki.path.components().count())
.map(|(idx, _)| idx)
}
pub fn find_wiki_by_path<P: AsRef<Path>>(
&self,
path: P,
) -> Option<&HtmlWikiConfig> {
self.find_wiki_index_by_path(path)
.and_then(|idx| self.find_wiki_by_index(idx))
}
pub fn map_runtime<F: FnOnce(HtmlRuntimeConfig) -> HtmlRuntimeConfig>(
&mut self,
f: F,
) {
self.runtime = f(self.runtime.clone());
}
}
#[derive(Clone, Debug)]
pub struct HtmlRuntimeConfig {
pub wiki_index: Option<usize>,
pub page: PathBuf,
}
impl HtmlRuntimeConfig {
pub fn to_tmp_wiki(&self) -> HtmlWikiConfig {
HtmlWikiConfig {
path: self
.page
.parent()
.map(Path::to_path_buf)
.unwrap_or_default(),
..Default::default()
}
}
}
impl Default for HtmlRuntimeConfig {
fn default() -> Self {
Self {
wiki_index: None,
page: HtmlWikiConfig::default_path().join("index.wiki"),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct HtmlWikiConfig {
#[serde(
default = "HtmlWikiConfig::default_path",
deserialize_with = "deserialize_absolute_path"
)]
pub path: PathBuf,
#[serde(
default = "HtmlWikiConfig::default_path_html",
deserialize_with = "deserialize_absolute_path"
)]
pub path_html: PathBuf,
#[serde(default = "HtmlWikiConfig::default_name")]
pub name: Option<String>,
#[serde(default = "HtmlWikiConfig::default_css_name")]
pub css_name: String,
#[serde(default = "HtmlWikiConfig::default_ext")]
pub ext: String,
#[serde(default = "HtmlWikiConfig::default_diary_rel_path")]
pub diary_rel_path: PathBuf,
}
impl Default for HtmlWikiConfig {
fn default() -> Self {
Self {
path: Self::default_path(),
path_html: Self::default_path_html(),
name: Self::default_name(),
css_name: Self::default_css_name(),
ext: Self::default_ext(),
diary_rel_path: Self::default_diary_rel_path(),
}
}
}
impl HtmlWikiConfig {
#[inline]
pub fn get_root_path(&self) -> &Path {
self.path.as_path()
}
pub fn path_to_root<P: AsRef<Path>>(&self, path: P) -> Option<PathBuf> {
self.path_within(path.as_ref()).and_then(|path| {
let hops_back = path.components().count();
if hops_back > 0 {
let mut rel_path = PathBuf::new();
for _ in 0..(hops_back - 1) {
rel_path.push(Component::ParentDir);
}
Some(rel_path)
} else {
None
}
})
}
pub fn path_within<'a>(&self, path: &'a Path) -> Option<&'a Path> {
path.strip_prefix(self.get_root_path()).ok()
}
pub fn make_output_path(&self, input: &Path, ext: &str) -> PathBuf {
let input =
if !input.has_root() || !input.starts_with(self.path.as_path()) {
self.path.join(make_path_relative(input))
} else {
input.to_path_buf()
};
let input = input
.strip_prefix(self.path.as_path())
.expect("Impossible: file should always be within wiki");
self.path_html.join(input).with_extension(ext)
}
#[inline]
pub fn default_path() -> PathBuf {
dirs::home_dir()
.unwrap_or_else(|| {
let mut path = PathBuf::new();
path.push(Component::RootDir);
path
})
.join("vimwiki")
}
#[inline]
pub fn default_path_html() -> PathBuf {
dirs::home_dir()
.unwrap_or_else(|| {
let mut path = PathBuf::new();
path.push(Component::RootDir);
path
})
.join("vimwiki_html")
}
#[inline]
pub const fn default_name() -> Option<String> {
None
}
#[inline]
pub fn default_css_name() -> String {
String::from("style.css")
}
#[inline]
pub fn default_ext() -> String {
String::from("wiki")
}
#[inline]
pub fn default_diary_rel_path() -> PathBuf {
PathBuf::from("diary")
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct HtmlListConfig {
#[serde(default = "HtmlListConfig::default_ignore_newline")]
pub ignore_newline: bool,
}
impl Default for HtmlListConfig {
fn default() -> Self {
Self {
ignore_newline: Self::default_ignore_newline(),
}
}
}
impl HtmlListConfig {
#[inline]
pub fn default_ignore_newline() -> bool {
true
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct HtmlParagraphConfig {
#[serde(default = "HtmlParagraphConfig::default_ignore_newline")]
pub ignore_newline: bool,
}
impl Default for HtmlParagraphConfig {
fn default() -> Self {
Self {
ignore_newline: Self::default_ignore_newline(),
}
}
}
impl HtmlParagraphConfig {
#[inline]
pub fn default_ignore_newline() -> bool {
true
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct HtmlLinkConfig {
#[serde(default = "HtmlLinkConfig::default_base_url", with = "uri")]
pub base_url: URI<'static>,
#[serde(default = "HtmlLinkConfig::default_canonicalize")]
pub canonicalize: bool,
#[serde(default = "HtmlLinkConfig::default_use_ugly_urls")]
pub use_ugly_urls: bool,
}
mod uri {
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std::convert::TryFrom;
use uriparse::URI;
pub fn serialize<S>(
data: &URI<'_>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
String::serialize(&data.to_string(), serializer)
}
pub fn deserialize<'de, D>(
deserializer: D,
) -> Result<URI<'static>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
URI::try_from(s.as_str()).map(URI::into_owned).map_err(|x| {
de::Error::invalid_value(
de::Unexpected::Str(&s),
&x.to_string().as_str(),
)
})
}
}
impl Default for HtmlLinkConfig {
fn default() -> Self {
Self {
base_url: Self::default_base_url(),
canonicalize: Self::default_canonicalize(),
use_ugly_urls: Self::default_use_ugly_urls(),
}
}
}
impl HtmlLinkConfig {
#[inline]
pub fn default_base_url() -> URI<'static> {
URI::try_from("https://localhost").unwrap().into_owned()
}
#[inline]
pub const fn default_canonicalize() -> bool {
false
}
#[inline]
pub const fn default_use_ugly_urls() -> bool {
false
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct HtmlHeaderConfig {
#[serde(default = "HtmlHeaderConfig::default_table_of_contents")]
pub table_of_contents: String,
}
impl Default for HtmlHeaderConfig {
fn default() -> Self {
Self {
table_of_contents: Self::default_table_of_contents(),
}
}
}
impl HtmlHeaderConfig {
#[inline]
pub fn default_table_of_contents() -> String {
String::from("Contents")
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct HtmlCodeConfig {
#[serde(default = "HtmlCodeConfig::default_theme")]
pub theme: String,
#[serde(default = "HtmlCodeConfig::default_theme_dir")]
pub theme_dir: Option<PathBuf>,
#[serde(default = "HtmlCodeConfig::default_server_side")]
pub server_side: bool,
#[serde(default = "HtmlCodeConfig::default_syntax_dir")]
pub syntax_dir: Option<PathBuf>,
}
impl Default for HtmlCodeConfig {
fn default() -> Self {
Self {
theme: Self::default_theme(),
theme_dir: Self::default_theme_dir(),
server_side: Self::default_server_side(),
syntax_dir: Self::default_syntax_dir(),
}
}
}
impl HtmlCodeConfig {
#[inline]
pub fn default_theme() -> String {
String::from("InspiredGitHub")
}
#[inline]
pub fn default_theme_dir() -> Option<PathBuf> {
None
}
#[inline]
pub fn default_server_side() -> bool {
false
}
#[inline]
pub fn default_syntax_dir() -> Option<PathBuf> {
None
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct HtmlCommentConfig {
#[serde(default = "HtmlCommentConfig::default_include")]
pub include: bool,
}
impl Default for HtmlCommentConfig {
fn default() -> Self {
Self {
include: Self::default_include(),
}
}
}
impl HtmlCommentConfig {
#[inline]
pub fn default_include() -> bool {
false
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct HtmlTemplateConfig {
#[serde(default = "HtmlTemplateConfig::default_name")]
pub name: String,
#[serde(default = "HtmlTemplateConfig::default_ext")]
pub ext: String,
#[serde(default = "HtmlTemplateConfig::default_dir")]
pub dir: PathBuf,
#[serde(default = "HtmlTemplateConfig::default_text")]
pub text: String,
}
impl Default for HtmlTemplateConfig {
fn default() -> Self {
Self {
name: Self::default_name(),
ext: Self::default_ext(),
dir: Self::default_dir(),
text: Self::default_text(),
}
}
}
impl HtmlTemplateConfig {
pub fn from_text(text: impl Into<String>) -> Self {
Self {
text: text.into(),
..Default::default()
}
}
#[inline]
pub fn default_name() -> String {
String::from("default")
}
#[inline]
pub fn default_ext() -> String {
String::from("tpl")
}
#[inline]
pub fn default_dir() -> PathBuf {
let mut path = PathBuf::new();
if let Some(dir) = dirs::home_dir() {
path.push(dir);
}
path.push("vimwiki");
path.push("templates");
path
}
#[inline]
pub fn default_text() -> String {
static DEFAULT_TEMPLATE_STR: &str = r#"<!DOCTYPE html>
<html>
<head>
<link rel="Stylesheet" type="text/css" href="%root_path%%css%">
<title>%title%</title>
<meta http-equiv="Content-Type" content="text/html; charset=%encoding%">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
%content%
</body>
</html>
"#;
DEFAULT_TEMPLATE_STR.to_string()
}
}