diff --git a/Cargo.toml b/Cargo.toml index e178095..6efb188 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,11 @@ [package] +description = "static site generator fit for a dog" edition = "2021" +homepage = "https://webdog.zyl.gay" +license = "AGPL-3.0-or-later" name = "webdog" +readme = "README.md" +repository = "https://github.com/zyllian/webdog" version = "0.1.0" [dependencies] diff --git a/README.md b/README.md index 8a9584c..d083a2d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,21 @@ # webdog -Source for webdog, the static site generator fit for a dog. See our website at https://webdog.zyl.gay for more details. +webdog, the static site generator fit for a dog :3 + +```sh +cargo install webdog +``` + +after installing, you can create your first site: + +```sh +webdog create https://example.com "My First Site!" --site my-site +cd my-site +webdog serve # your site is now running at http://127.0.0.1:8080 🥳 +``` + +from there, you can start editing your site and adding pages or [more advanced things](https://webdog.zyl.gay/docs/) + +```sh +webdog page new my-first-page "My First Page" +``` diff --git a/site/pages/index.md b/site/pages/index.md index 78e9937..0c4de07 100644 --- a/site/pages/index.md +++ b/site/pages/index.md @@ -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: ```sh -cargo install webdog --git https://github.com/zyllian/webdog +cargo install webdog ``` then you can make your first webdog site! diff --git a/site/sass/index.scss b/site/sass/index.scss index 4ebf304..13c1808 100644 --- a/site/sass/index.scss +++ b/site/sass/index.scss @@ -183,6 +183,7 @@ abbr { .wd-codeblock { position: relative; + tab-size: 2; .copy { display: none; @@ -206,6 +207,6 @@ abbr { & > pre { padding: 8px; - overflow: scroll; + overflow: auto; } } diff --git a/src/builder.rs b/src/builder.rs index 2222afe..d5da847 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -4,7 +4,6 @@ use std::{collections::HashMap, path::PathBuf}; use eyre::{eyre, Context, OptionExt}; use lol_html::{element, html_content::ContentType, HtmlRewriter, Settings}; -use pulldown_cmark::{Options, Parser}; use rayon::prelude::*; use serde::Serialize; use syntect::{highlighting::ThemeSet, parsing::SyntaxSet}; @@ -261,10 +260,28 @@ impl SiteBuilder { Ok(()) }), + element!("img", |el| { + if let Some(mut src) = el.get_attribute("src") { + if let Some((command, new_src)) = src.split_once('$') { + let mut new_src = new_src.to_string(); + #[allow(clippy::single_match)] + match command { + "cdn" => { + new_src = self.site.config.cdn_url(&new_src)?.to_string(); + } + _ => new_src = src, + } + src = new_src; + el.set_attribute("src", &src)?; + } + } + + Ok(()) + }), element!("a", |el| { if let Some(mut href) = el.get_attribute("href") { - if let Some((command, mut new_href)) = href.split_once('$') { - #[allow(clippy::single_match)] + if let Some((command, new_href)) = href.split_once('$') { + let mut new_href = new_href.to_string(); match command { "me" => { el.set_attribute( @@ -273,11 +290,14 @@ impl SiteBuilder { + " me"), )?; } + "cdn" => { + new_href = self.site.config.cdn_url(&new_href)?.to_string(); + } _ => { - new_href = &href; + new_href = href; } } - href = new_href.to_string(); + href = new_href; el.set_attribute("href", &href)?; } if let Ok(url) = Url::parse(&href) { @@ -294,47 +314,6 @@ impl SiteBuilder { } } - Ok(()) - }), - element!("md", |el| { - el.remove(); - let class = el.get_attribute("class"); - - let md_type = el - .get_attribute("type") - .ok_or_eyre("missing type attribute on markdown tag")?; - - if md_type == "blog-image" { - let mut src = el - .get_attribute("src") - .ok_or_eyre("missing src attribute")?; - - if src.starts_with("cdn$") { - src = self.site.config.cdn_url(&src[4..])?.to_string(); - } - - let class = format!("image {}", class.unwrap_or_default()); - let content = el - .get_attribute("content") - .ok_or_eyre("missing content attribute")?; - - el.replace( - &format!( - r#" -
- - - - {content} -
- "# - ), - ContentType::Html, - ); - } else { - return Err(eyre!("unknown markdown tag type: {md_type}").into()); - } - Ok(()) }), ], @@ -419,49 +398,7 @@ impl SiteBuilder { .with_context(|| format!("Failed to read page at {}", page_path.display()))?; let page = crate::frontmatter::FrontMatter::parse(input)?; - let mut language = None; - let parser = Parser::new_ext(&page.content, Options::all()).filter_map(|event| { - // syntax highlighting for code blocks - match event { - pulldown_cmark::Event::Start(pulldown_cmark::Tag::CodeBlock( - pulldown_cmark::CodeBlockKind::Fenced(name), - )) => { - language = Some(name); - None - } - pulldown_cmark::Event::Text(code) => { - if let Some(language) = language.take() { - let syntax_reference = self - .syntax_set - .find_syntax_by_token(&language) - .unwrap_or_else(|| self.syntax_set.find_syntax_plain_text()); - let html = format!( - r#"
- - {} -
"#, - syntect::html::highlighted_html_for_string( - &code, - &self.syntax_set, - syntax_reference, - self.theme_set - .themes - .get(&self.site.config.code_theme) - .as_ref() - .expect("should never fail"), - ) - .expect("failed to highlight syntax") - ); - Some(pulldown_cmark::Event::Html(html.into())) - } else { - Some(pulldown_cmark::Event::Text(code)) - } - } - _ => Some(event), - } - }); - let mut page_html = String::new(); - pulldown_cmark::html::push_html(&mut page_html, parser); + let page_html = util::render_markdown(self, &page.content)?; let out = self.build_page_raw(page.data.unwrap_or_default(), &page_html, ())?; diff --git a/src/embedded/default_site/sass/index.scss b/src/embedded/default_site/sass/index.scss index 239ad7e..06ef3d6 100644 --- a/src/embedded/default_site/sass/index.scss +++ b/src/embedded/default_site/sass/index.scss @@ -5,6 +5,7 @@ .wd-codeblock { position: relative; + tab-size: 2; .copy { display: none; @@ -28,6 +29,6 @@ & > pre { padding: 8px; - overflow: scroll; + overflow: auto; } } diff --git a/src/resource.rs b/src/resource.rs index d4d5bf1..433584c 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -5,14 +5,13 @@ use std::{ use eyre::Context; use itertools::Itertools; -use pulldown_cmark::{Options, Parser}; use rss::{validation::Validate, ChannelBuilder, ItemBuilder}; use serde::{Deserialize, Serialize}; use time::{format_description::well_known::Rfc2822, OffsetDateTime}; use crate::{ builder::SiteBuilder, frontmatter::FrontMatterRequired, link_list::Link, - util::format_timestamp, PageMetadata, + util::{self, format_timestamp}, PageMetadata, }; /// Metadata for resources. @@ -182,10 +181,7 @@ impl ResourceBuilder { let mut page = FrontMatterRequired::::parse(input) .wrap_err_with(|| eyre::eyre!("Failed to parse resource front matter"))?; - let parser = Parser::new_ext(&page.content, Options::all()); - let mut html = String::new(); - pulldown_cmark::html::push_html(&mut html, parser); - *page.content_mut() = html; + *page.content_mut() = util::render_markdown(builder, &page.content)?; let data = page.data_mut(); if let Some(cdn_file) = &data.cdn_file { diff --git a/src/util.rs b/src/util.rs index d8ebf18..0f42680 100644 --- a/src/util.rs +++ b/src/util.rs @@ -2,8 +2,11 @@ use std::path::Path; +use pulldown_cmark::{Options, Parser}; use time::OffsetDateTime; +use crate::builder::SiteBuilder; + /// Simple helper to remove the contents of a directory without removing the directory itself. pub fn remove_dir_contents(path: &Path) -> eyre::Result<()> { for entry in path.read_dir()? { @@ -24,3 +27,52 @@ pub fn format_timestamp(ts: OffsetDateTime, format: &str) -> eyre::Result(format)?; Ok(ts.format(&fmt)?) } + +/// Helper to render markdown. +pub fn render_markdown(builder: &SiteBuilder, input: &str) -> eyre::Result { + let mut language = None; + let parser = Parser::new_ext(input, Options::all()).filter_map(|event| { + // syntax highlighting for code blocks + match event { + pulldown_cmark::Event::Start(pulldown_cmark::Tag::CodeBlock( + pulldown_cmark::CodeBlockKind::Fenced(name), + )) => { + language = Some(name); + None + } + pulldown_cmark::Event::Text(code) => { + if let Some(language) = language.take() { + let syntax_reference = builder + .syntax_set + .find_syntax_by_token(&language) + .unwrap_or_else(|| builder.syntax_set.find_syntax_plain_text()); + let html = format!( + r#"
+ + {} +
"#, + syntect::html::highlighted_html_for_string( + &code, + &builder.syntax_set, + syntax_reference, + builder.theme_set + .themes + .get(&builder.site.config.code_theme) + .as_ref() + .expect("should never fail"), + ) + .expect("failed to highlight syntax") + ); + Some(pulldown_cmark::Event::Html(html.into())) + } else { + Some(pulldown_cmark::Event::Text(code)) + } + } + _ => Some(event), + } + }); + let mut page_html = String::new(); + pulldown_cmark::html::push_html(&mut page_html, parser); + + Ok(page_html) +}