mod asis;
pub use asis::AsIs;
mod feed;
pub use feed::{Feed, FeedKind};
mod git;
pub use git::GitRepo;
mod page;
pub use page::Pages;
mod post;
pub use post::Posts;
use std::{
fmt::{self, Debug},
io::{self, Write},
ops::Deref,
path::{self, Path, PathBuf},
str::FromStr,
};
use crate::{Blog, Metadata};
pub trait Serve: Debug + Send + Sync {
fn base(&self) -> &Url;
fn scan(&self, dir: &Path) -> io::Result<Metadata>;
fn render(&self, blog: &Blog, url: &Url, into: &mut dyn Write) -> io::Result<()>;
fn post_body(&self, _: &Blog, _: &Url, _: &mut dyn Write) -> io::Result<()> {
Err(io::Error::new(io::ErrorKind::NotFound, "this serve doesn't serve any posts"))
}
}
#[derive(Clone, Default, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(transparent)]
pub struct Url(pub PathBuf);
impl Url {
pub fn new(p: impl Into<PathBuf>) -> Self {
Self(p.into())
}
pub fn str(&self) -> &str {
self.to_str().unwrap()
}
}
impl<T: AsRef<Path>> From<T> for Url {
fn from(value: T) -> Self {
Self(value.as_ref().to_path_buf())
}
}
impl Deref for Url {
type Target = Path;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl fmt::Display for Url {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0.to_str().unwrap())
}
}
impl fmt::Debug for Url {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "url:{:?}", self.0)
}
}
#[derive(Clone, Default, Hash, PartialEq, Eq)]
pub struct Dir(pub PathBuf);
impl Dir {
pub fn new(p: impl Into<PathBuf>) -> Self {
Self(p.into())
}
}
impl<T: AsRef<Path>> From<T> for Dir {
fn from(value: T) -> Self {
Self(value.as_ref().to_path_buf())
}
}
impl Deref for Dir {
type Target = Path;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl fmt::Display for Dir {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0.to_str().unwrap())
}
}
impl fmt::Debug for Dir {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "dir:{:?}", self.0)
}
}
#[derive(Clone, Debug, Default, serde::Serialize)]
pub struct NavItem {
pub text: String,
pub href: String,
}
impl NavItem {
pub fn new(text: impl Into<String>, href: impl Into<String>) -> Self {
Self {
text: text.into(),
href: href.into(),
}
}
}
impl From<(String, String)> for NavItem {
fn from((text, href): (String, String)) -> Self {
Self::new(text, href)
}
}
pub trait HasNav {
fn get_nav(&mut self) -> &mut Vec<NavItem>;
fn nav(mut self, text: impl Into<String>, href: impl Into<String>) -> Self
where
Self: Sized,
{
self.get_nav().push(NavItem::new(text, href));
self
}
fn navs(
mut self,
vals: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
) -> Self
where
Self: Sized,
{
self.get_nav().extend(vals.into_iter().map(|(t, h)| NavItem::new(t, h)));
self
}
}
#[derive(Clone)]
pub enum CssRule {
Include(glob::Pattern),
Exclude(glob::Pattern),
}
impl FromStr for CssRule {
type Err = glob::PatternError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (excl, s) = match s.strip_prefix('!') {
Some(s) => (true, s),
None => (false, s),
};
let pat = s.parse()?;
if excl {
Ok(Self::Exclude(pat))
} else {
Ok(Self::Include(pat))
}
}
}
impl<T: AsRef<str>> From<T> for CssRule {
fn from(value: T) -> Self {
value.as_ref().parse().unwrap()
}
}
impl fmt::Display for CssRule {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Include(p) => write!(f, "{p}"),
Self::Exclude(p) => write!(f, "!{p}"),
}
}
}
impl fmt::Debug for CssRule {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Include(p) => write!(f, "CssRule({:?})", p.as_str()),
Self::Exclude(p) => write!(f, "CssRule(!{:?})", p.as_str()),
}
}
}
pub trait HasCssRules {
fn get_rules(&mut self) -> &mut Vec<CssRule>;
fn add_rule(mut self, rule: CssRule) -> Self
where
Self: Sized,
{
self.get_rules().push(rule);
self
}
fn add_rules(mut self, rules: impl IntoIterator<Item = impl Into<CssRule>>) -> Self
where
Self: Sized,
{
self.get_rules().extend(rules.into_iter().map(Into::into));
self
}
fn include_css(mut self, rule: impl AsRef<str>) -> Self
where
Self: Sized,
{
let pat = rule.as_ref().parse().unwrap();
self.get_rules().push(CssRule::Include(pat));
self
}
fn exclude_css(mut self, rule: impl AsRef<str>) -> Self
where
Self: Sized,
{
let pat = rule.as_ref().parse().unwrap();
self.get_rules().push(CssRule::Exclude(pat));
self
}
}
#[derive(Debug, Default)]
pub struct Mount {
url: Url,
dir: Dir,
}
impl Mount {
fn relify(path: PathBuf) -> PathBuf {
if !path.is_absolute() {
return path;
}
let mut new = PathBuf::new();
for component in path.components() {
match component {
path::Component::Normal(n) => new.push(n),
path::Component::ParentDir => {
new.pop();
}
_ => (), }
}
new
}
pub fn new(url: impl Into<Url>, dir: impl Into<Dir>) -> Self {
Self {
url: Url(Self::relify(url.into().0)),
dir: Dir(Self::relify(dir.into().0)),
}
}
pub fn dir(&self, from: &Url) -> Option<Dir> {
from.0.strip_prefix(&self.url.0).ok().map(|rel| Dir::new(self.dir.0.join(rel)))
}
pub fn url(&self, from: &Dir) -> Option<Url> {
from.0.strip_prefix(&self.dir.0).ok().map(|rel| Url::new(self.url.0.join(rel)))
}
}