Compare commits

..

No commits in common. "main" and "v0.1.0" have entirely different histories.
main ... v0.1.0

12 changed files with 606 additions and 573 deletions

1012
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,42 +1,47 @@
[package] [package]
description = "static site generator fit for a dog" description = "static site generator fit for a dog"
edition = "2024" edition = "2021"
homepage = "https://webdog.zyl.gay" homepage = "https://webdog.zyl.gay"
license = "AGPL-3.0-or-later" license = "AGPLv2"
name = "webdog" name = "webdog"
readme = "README.md" readme = "README.md"
repository = "https://github.com/zyllian/webdog" repository = "https://github.com/zyllian/webdog"
version = "0.1.3" version = "0.1.0"
[dependencies] [dependencies]
clap = { version = "4", features = ["derive"] } clap = {version = "4", features = ["derive"]}
color-eyre = { version = "0.6", optional = true } color-eyre = {version = "0.6", optional = true}
extract-frontmatter = "4" extract-frontmatter = "4"
eyre = "0.6" eyre = "0.6"
futures = { version = "0.3", optional = true } fs_extra = "1.2"
grass = { version = "0.13", default-features = false } futures = {version = "0.3", optional = true}
hotwatch = { version = "0.5", optional = true } grass = {version = "0.13", default-features = false}
html5ever = "0.31" hotwatch = {version = "0.5", optional = true}
html5ever = "0.29"
include_dir = "0.7" include_dir = "0.7"
kuchikiki = "0.8.8-speedreader" itertools = "0.14"
kuchikiki = "0.8.6-speedreader"
lol_html = "2" lol_html = "2"
minifier = { version = "0.3", features = ["html"] } minifier = {version = "0.3", features = ["html"]}
percent-encoding = { version = "2", optional = true } percent-encoding = {version = "2", optional = true}
pulldown-cmark = { version = "0.13", default-features = false, features = [ pulldown-cmark = {version = "0.12", default-features = false, features = [
"simd", "simd",
"html", "html",
] } ]}
rayon = "1" rayon = "1"
rss = { version = "2", features = ["validation"] } rss = {version = "2", features = ["validation"]}
serde = { version = "1", features = ["derive"] } serde = {version = "1", features = ["derive"]}
serde_yaml_ng = "0.10" serde_yml = "0.0.12"
syntect = "5" syntect = "5"
tera = "1" tera = "1"
time = { version = "0.3", features = ["serde-human-readable"] } time = {version = "0.3", features = ["serde-human-readable"]}
tokio = { version = "1", features = ["rt-multi-thread"], optional = true } tokio = {version = "1", features = [
url = { version = "2", features = ["serde"] } "macros",
"rt-multi-thread",
], optional = true}
url = {version = "2", features = ["serde"]}
walkdir = "2" walkdir = "2"
warp = { version = "0.3", optional = true } warp = {version = "0.3", optional = true}
[features] [features]
default = ["serve", "color-eyre"] default = ["serve", "color-eyre"]

View file

@ -3,7 +3,7 @@
webdog, the static site generator fit for a dog :3 webdog, the static site generator fit for a dog :3
```sh ```sh
cargo install webdog --locked cargo install webdog
``` ```
after installing, you can create your first site: after installing, you can create your first site:

View file

@ -37,10 +37,6 @@ list of sass/scss stylesheets in the site's `sass` directory to treat as root st
base url for the various cdn url transformation features of webdog. base url for the various cdn url transformation features of webdog.
## `webdog_path`
optional custom path for webdog's static resources.
## `code_theme` ## `code_theme`
the theme to use for code blocks. valid options: `base16-ocean.dark`, `base16-eighties.dark`, `base16-mocha.dark`, `base16-ocean.light`, `InspiredGitHub`, `Solarized (dark)`, and `Solarized (light)` the theme to use for code blocks. valid options: `base16-ocean.dark`, `base16-eighties.dark`, `base16-mocha.dark`, `base16-ocean.light`, `InspiredGitHub`, `Solarized (dark)`, and `Solarized (light)`

View file

@ -5,7 +5,7 @@ welcome to webdog, the static site generator fit for a dog :3
if you have [rust](https://rust-lang.org) installed, all you need to do to install webdog is run the following command: if you have [rust](https://rust-lang.org) installed, all you need to do to install webdog is run the following command:
```sh ```sh
cargo install webdog --locked cargo install webdog --git https://github.com/zyllian/webdog
``` ```
then you can make your first webdog site! then you can make your first webdog site!

View file

@ -2,18 +2,18 @@
use std::{collections::HashMap, path::PathBuf}; use std::{collections::HashMap, path::PathBuf};
use eyre::{Context, OptionExt, eyre}; use eyre::{eyre, Context, OptionExt};
use lol_html::{HtmlRewriter, Settings, element, html_content::ContentType}; use lol_html::{element, html_content::ContentType, HtmlRewriter, Settings};
use rayon::prelude::*; use rayon::prelude::*;
use serde::Serialize; use serde::Serialize;
use syntect::{highlighting::ThemeSet, parsing::SyntaxSet}; use syntect::{highlighting::ThemeSet, parsing::SyntaxSet};
use tera::Tera; use tera::Tera;
use url::Url; use url::Url;
use crate::{PageMetadata, ROOT_PATH, SASS_PATH, Site, resource::ResourceBuilder, util}; use crate::{resource::ResourceBuilder, util, PageMetadata, Site, ROOT_PATH, SASS_PATH};
/// Default path for static webdog resources included with the site build. /// Path for static webdog resources included with the site build.
const WEBDOG_DEFAULT_PATH: &str = "webdog"; const WEBDOG_PATH: &str = "webdog";
/// Struct containing data to be sent to templates when rendering them. /// Struct containing data to be sent to templates when rendering them.
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
@ -25,7 +25,7 @@ struct TemplateData<'a, T> {
/// Custom template data. /// Custom template data.
pub data: T, pub data: T,
/// Userdata supplied from the page. /// Userdata supplied from the page.
pub userdata: serde_yaml_ng::Value, pub userdata: serde_yml::Value,
} }
/// Struct used to build the site. /// Struct used to build the site.
@ -96,13 +96,7 @@ impl SiteBuilder {
std::fs::create_dir(&self.build_path).wrap_err("Failed to create build directory")?; std::fs::create_dir(&self.build_path).wrap_err("Failed to create build directory")?;
} }
let webdog_path = self.build_path.join( let webdog_path = self.build_path.join(WEBDOG_PATH);
self.site
.config
.webdog_path
.clone()
.unwrap_or_else(|| WEBDOG_DEFAULT_PATH.to_string()),
);
std::fs::create_dir(&webdog_path)?; std::fs::create_dir(&webdog_path)?;
std::fs::write( std::fs::write(
webdog_path.join("webdog.js"), webdog_path.join("webdog.js"),
@ -161,7 +155,6 @@ impl SiteBuilder {
} }
/// Function to rewrite HTML wow. /// Function to rewrite HTML wow.
#[allow(clippy::too_many_arguments)]
pub fn rewrite_html( pub fn rewrite_html(
&self, &self,
html: String, html: String,
@ -170,7 +163,6 @@ impl SiteBuilder {
scripts: &[String], scripts: &[String],
styles: &[String], styles: &[String],
is_partial: bool, is_partial: bool,
webdog_path: &str,
) -> eyre::Result<String> { ) -> eyre::Result<String> {
use kuchikiki::traits::*; use kuchikiki::traits::*;
@ -197,7 +189,7 @@ impl SiteBuilder {
let new_html = self.build_page_raw( let new_html = self.build_page_raw(
PageMetadata { PageMetadata {
template: Some(template.to_string()), template: Some(template.to_string()),
userdata: serde_yaml_ng::to_value(attr_map)?, userdata: serde_yml::to_value(attr_map)?,
is_partial: true, is_partial: true,
..Default::default() ..Default::default()
}, },
@ -259,9 +251,7 @@ impl SiteBuilder {
); );
} }
el.append( el.append(
&format!( r#"<script type="text/javascript" src="/webdog/webdog.js" defer></script>"#,
r#"<script type="text/javascript" src="/{webdog_path}/webdog.js" defer></script>"#
),
ContentType::Html, ContentType::Html,
); );
if self.serving { if self.serving {
@ -277,8 +267,7 @@ impl SiteBuilder {
#[allow(clippy::single_match)] #[allow(clippy::single_match)]
match command { match command {
"cdn" => { "cdn" => {
new_src = new_src = self.site.config.cdn_url(&new_src)?.to_string();
self.site.config.cdn_url(&new_src)?.to_string();
} }
_ => new_src = src, _ => new_src = src,
} }
@ -302,8 +291,7 @@ impl SiteBuilder {
)?; )?;
} }
"cdn" => { "cdn" => {
new_href = new_href = self.site.config.cdn_url(&new_href)?.to_string();
self.site.config.cdn_url(&new_href)?.to_string();
} }
_ => { _ => {
new_href = href; new_href = href;
@ -387,12 +375,6 @@ impl SiteBuilder {
&page_metadata.scripts, &page_metadata.scripts,
&page_metadata.styles, &page_metadata.styles,
page_metadata.is_partial, page_metadata.is_partial,
&self
.site
.config
.webdog_path
.clone()
.unwrap_or_else(|| WEBDOG_DEFAULT_PATH.to_string()),
)?; )?;
if let Some(data) = extra { if let Some(data) = extra {

View file

@ -1,4 +1,5 @@
use lol_html::{RewriteStrSettings, element}; use itertools::Itertools;
use lol_html::{element, RewriteStrSettings};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{builder::SiteBuilder, resource::ResourceTemplateData}; use crate::{builder::SiteBuilder, resource::ResourceTemplateData};
@ -29,7 +30,7 @@ impl Extra {
match self { match self {
Self::Basic => { Self::Basic => {
let data: BasicData = serde_yaml_ng::from_value(data.inner.clone())?; let data: BasicData = serde_yml::from_value(data.inner.clone())?;
let content = builder.tera.render(&data.template, &tera::Context::new())?; let content = builder.tera.render(&data.template, &tera::Context::new())?;
append_to(&page, &content, "main.page") append_to(&page, &content, "main.page")
} }
@ -45,7 +46,7 @@ pub struct ExtraData {
pub name: String, pub name: String,
/// The inner data for the extra. /// The inner data for the extra.
#[serde(flatten)] #[serde(flatten)]
pub inner: serde_yaml_ng::Value, pub inner: serde_yml::Value,
} }
/// Gets the extra for the given value. /// Gets the extra for the given value.
@ -89,7 +90,7 @@ fn resource_list_outside(
resources: Vec<ResourceTemplateData<'r>>, resources: Vec<ResourceTemplateData<'r>>,
} }
let data: ResourceListData = serde_yaml_ng::from_value(data.inner.clone())?; let data: ResourceListData = serde_yml::from_value(data.inner.clone())?;
let res_builder = builder let res_builder = builder
.resource_builders .resource_builders
@ -110,13 +111,11 @@ fn resource_list_outside(
) )
.map(|ts| (id, v, ts)) .map(|ts| (id, v, ts))
}) })
.map(|v| { .map_ok(|(id, v, ts)| ResourceTemplateData {
v.map(|(id, v, ts)| ResourceTemplateData {
resource: v, resource: v,
id: id.clone(), id: id.clone(),
readable_timestamp: ts, readable_timestamp: ts,
}) })
})
.collect::<eyre::Result<Vec<_>>>()?, .collect::<eyre::Result<Vec<_>>>()?,
})?, })?,
)?; )?;

View file

@ -32,7 +32,7 @@ where
pub fn parse(input: String) -> eyre::Result<Self> { pub fn parse(input: String) -> eyre::Result<Self> {
if input.starts_with("---\n") { if input.starts_with("---\n") {
if let Some((frontmatter, content)) = input[3..].split_once("\n---\n") { if let Some((frontmatter, content)) = input[3..].split_once("\n---\n") {
let data = serde_yaml_ng::from_str(frontmatter)?; let data = serde_yml::from_str(frontmatter)?;
return Ok(Self { return Ok(Self {
content: content.to_string(), content: content.to_string(),
data, data,
@ -56,7 +56,7 @@ where
if let Some(data) = &self.data { if let Some(data) = &self.data {
output.push_str("---\n"); output.push_str("---\n");
output.push_str(&serde_yaml_ng::to_string(data)?); output.push_str(&serde_yml::to_string(data)?);
output.push_str("---\n\n"); output.push_str("---\n\n");
} }

View file

@ -49,8 +49,6 @@ pub struct SiteConfig {
pub sass_styles: Vec<PathBuf>, pub sass_styles: Vec<PathBuf>,
/// URL to the CDN used for the site's images. /// URL to the CDN used for the site's images.
pub cdn_url: Url, pub cdn_url: Url,
/// The path to output webdog static resources to. Defaults to "webdog"
pub webdog_path: Option<String>,
/// The theme to use for the site's code blocks. /// The theme to use for the site's code blocks.
/// TODO: dark/light themes /// TODO: dark/light themes
/// TODO: export themes as CSS instead of styling HTML directly /// TODO: export themes as CSS instead of styling HTML directly
@ -75,7 +73,6 @@ impl SiteConfig {
build: None, build: None,
sass_styles: vec!["index.scss".into()], sass_styles: vec!["index.scss".into()],
cdn_url, cdn_url,
webdog_path: None,
code_theme: "base16-ocean.dark".to_string(), code_theme: "base16-ocean.dark".to_string(),
resources: Default::default(), resources: Default::default(),
} }
@ -103,9 +100,7 @@ impl SiteConfig {
if !config_path.exists() { if !config_path.exists() {
eyre::bail!("no site config found!"); eyre::bail!("no site config found!");
} }
Ok(serde_yaml_ng::from_str(&std::fs::read_to_string( Ok(serde_yml::from_str(&std::fs::read_to_string(config_path)?)?)
config_path,
)?)?)
} }
} }
@ -134,7 +129,7 @@ pub struct PageMetadata {
pub extra: Option<ExtraData>, pub extra: Option<ExtraData>,
/// Custom values passed to the base template. /// Custom values passed to the base template.
#[serde(default)] #[serde(default)]
pub userdata: serde_yaml_ng::Value, pub userdata: serde_yml::Value,
/// Whether this page being rendered is a partial. Set by the builder, not your page metadata. /// Whether this page being rendered is a partial. Set by the builder, not your page metadata.
#[serde(skip)] #[serde(skip)]
pub is_partial: bool, pub is_partial: bool,

View file

@ -135,7 +135,7 @@ fn main() -> eyre::Result<()> {
let config = SiteConfig::new(base_url.clone(), cdn_url.unwrap_or(base_url), title); let config = SiteConfig::new(base_url.clone(), cdn_url.unwrap_or(base_url), title);
std::fs::write( std::fs::write(
cli.site.join(SiteConfig::FILENAME), cli.site.join(SiteConfig::FILENAME),
serde_yaml_ng::to_string(&config)?, serde_yml::to_string(&config)?,
)?; )?;
DEFAULT_PROJECT.extract(&cli.site)?; DEFAULT_PROJECT.extract(&cli.site)?;
std::fs::create_dir(cli.site.join(webdog::ROOT_PATH))?; std::fs::create_dir(cli.site.join(webdog::ROOT_PATH))?;
@ -227,7 +227,7 @@ fn main() -> eyre::Result<()> {
config.resources.insert(id.clone(), resource_config); config.resources.insert(id.clone(), resource_config);
std::fs::write(config_path, serde_yaml_ng::to_string(&config)?)?; std::fs::write(config_path, serde_yml::to_string(&config)?)?;
let resource_path = cli.site.join(webdog::RESOURCES_PATH).join(&id); let resource_path = cli.site.join(webdog::RESOURCES_PATH).join(&id);
std::fs::create_dir_all(&resource_path)?; std::fs::create_dir_all(&resource_path)?;
@ -240,7 +240,7 @@ fn main() -> eyre::Result<()> {
tags: vec!["first".to_string()], tags: vec!["first".to_string()],
cdn_file: None, cdn_file: None,
desc: Some(format!("This is the first {name} :)")), desc: Some(format!("This is the first {name} :)")),
inner: serde_yaml_ng::Value::Null, inner: serde_yml::Value::Null,
draft: true, draft: true,
}, },
)?; )?;
@ -256,11 +256,7 @@ fn main() -> eyre::Result<()> {
title, title,
template, template,
} => { } => {
let page_path = cli let page_path = cli.site.join(webdog::PAGES_PATH).join(&id).with_extension("md");
.site
.join(webdog::PAGES_PATH)
.join(&id)
.with_extension("md");
let dir = page_path.parent().expect("should never fail"); let dir = page_path.parent().expect("should never fail");
if page_path.exists() { if page_path.exists() {
eprintln!("page already exists!"); eprintln!("page already exists!");
@ -314,7 +310,7 @@ fn main() -> eyre::Result<()> {
tags, tags,
cdn_file: None, cdn_file: None,
desc: description, desc: description,
inner: serde_yaml_ng::Value::Null, inner: serde_yml::Value::Null,
draft: !skip_draft, draft: !skip_draft,
}, },
)?; )?;

View file

@ -4,16 +4,14 @@ use std::{
}; };
use eyre::Context; use eyre::Context;
use rss::{ChannelBuilder, ItemBuilder, validation::Validate}; use itertools::Itertools;
use rss::{validation::Validate, ChannelBuilder, ItemBuilder};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use time::{OffsetDateTime, format_description::well_known::Rfc2822}; use time::{format_description::well_known::Rfc2822, OffsetDateTime};
use crate::{ use crate::{
PageMetadata, builder::SiteBuilder, frontmatter::FrontMatterRequired, link_list::Link,
builder::SiteBuilder, util::{self, format_timestamp}, PageMetadata,
frontmatter::FrontMatterRequired,
link_list::Link,
util::{self, format_timestamp},
}; };
/// Metadata for resources. /// Metadata for resources.
@ -32,7 +30,7 @@ pub struct ResourceMetadata {
pub desc: Option<String>, pub desc: Option<String>,
/// Extra resource data not included. /// Extra resource data not included.
#[serde(flatten)] #[serde(flatten)]
pub inner: serde_yaml_ng::Value, pub inner: serde_yml::Value,
/// Whether the resource is a draft. Drafts can be committed without being published to the live site. /// Whether the resource is a draft. Drafts can be committed without being published to the live site.
#[serde(default)] #[serde(default)]
pub draft: bool, pub draft: bool,
@ -318,7 +316,7 @@ impl ResourceBuilder {
let page_max = list.len() / items_per_page + (list.len() % items_per_page).min(1); let page_max = list.len() / items_per_page + (list.len() % items_per_page).min(1);
let mut previous = None; let mut previous = None;
let mut next; let mut next;
for (page, iter) in list.chunks(items_per_page).enumerate() { for (page, iter) in list.iter().chunks(items_per_page).into_iter().enumerate() {
next = (page + 1 != page_max).then_some(page + 2); next = (page + 1 != page_max).then_some(page + 2);
let out = builder.build_page_raw( let out = builder.build_page_raw(
PageMetadata { PageMetadata {
@ -328,7 +326,7 @@ impl ResourceBuilder {
}, },
"", "",
ResourceListTemplateData { ResourceListTemplateData {
resources: iter.to_vec(), resources: iter.copied().collect(),
tag, tag,
rss_enabled: config.rss.is_some(), rss_enabled: config.rss.is_some(),
page: page + 1, page: page + 1,
@ -371,7 +369,7 @@ impl ResourceBuilder {
// Build list of tags // Build list of tags
{ {
let mut links: Vec<_> = tags let links = tags
.iter() .iter()
.map(|(tag, data)| { .map(|(tag, data)| {
let count = data.len(); let count = data.len();
@ -383,9 +381,9 @@ impl ResourceBuilder {
count, count,
) )
}) })
.sorted_by(|(_, a), (_, b)| b.cmp(a))
.map(|(l, _)| l)
.collect(); .collect();
links.sort_by(|(_, a), (_, b)| b.cmp(a));
let links = links.into_iter().map(|(l, _)| l).collect();
let out = crate::link_list::render_basic_link_list( let out = crate::link_list::render_basic_link_list(
builder, builder,
&self.config.tag_list_template, &self.config.tag_list_template,

View file

@ -95,7 +95,7 @@ fn create(
builder.build_all_resources()?; builder.build_all_resources()?;
} }
} else if relative_path.display().to_string() == SiteConfig::FILENAME { } else if relative_path.display().to_string() == SiteConfig::FILENAME {
let new_config = serde_yaml_ng::from_str(&std::fs::read_to_string(path)?)?; let new_config = serde_yml::from_str(&std::fs::read_to_string(path)?)?;
builder.site.config = new_config; builder.site.config = new_config;
builder.reload()?; builder.reload()?;
builder.build_all()?; builder.build_all()?;