mirror of
https://github.com/zyllian/webdog.git
synced 2025-05-10 02:26:42 -07:00
introduce webdog cli tool
This commit is contained in:
parent
8b7620b0bd
commit
399a0c1b87
26 changed files with 528 additions and 108 deletions
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
|
@ -40,7 +40,7 @@ jobs:
|
||||||
# Runs a single command using the runners shell
|
# Runs a single command using the runners shell
|
||||||
- name: Build site
|
- name: Build site
|
||||||
run: |
|
run: |
|
||||||
cargo run --no-default-features --release
|
cargo run --no-default-features --release -- build --site-path site
|
||||||
|
|
||||||
- name: Setup Pages
|
- name: Setup Pages
|
||||||
uses: actions/configure-pages@v5
|
uses: actions/configure-pages@v5
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
/target
|
/target
|
||||||
/site/build
|
/site/build
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
/sitetest
|
||||||
|
|
40
Cargo.lock
generated
40
Cargo.lock
generated
|
@ -280,6 +280,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
|
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -294,6 +295,18 @@ dependencies = [
|
||||||
"strsim",
|
"strsim",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.5.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.87",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_lex"
|
name = "clap_lex"
|
||||||
version = "0.7.2"
|
version = "0.7.2"
|
||||||
|
@ -937,6 +950,12 @@ dependencies = [
|
||||||
"http 0.2.12",
|
"http 0.2.12",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
@ -1216,6 +1235,25 @@ dependencies = [
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "include_dir"
|
||||||
|
version = "0.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd"
|
||||||
|
dependencies = [
|
||||||
|
"include_dir_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "include_dir_macros"
|
||||||
|
version = "0.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indenter"
|
name = "indenter"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
@ -2838,6 +2876,7 @@ checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
|
||||||
name = "webdog"
|
name = "webdog"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"clap",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"extract-frontmatter",
|
"extract-frontmatter",
|
||||||
"eyre",
|
"eyre",
|
||||||
|
@ -2845,6 +2884,7 @@ dependencies = [
|
||||||
"futures",
|
"futures",
|
||||||
"grass",
|
"grass",
|
||||||
"hotwatch",
|
"hotwatch",
|
||||||
|
"include_dir",
|
||||||
"itertools",
|
"itertools",
|
||||||
"lol_html",
|
"lol_html",
|
||||||
"minifier",
|
"minifier",
|
||||||
|
|
|
@ -4,6 +4,7 @@ name = "webdog"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
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"
|
||||||
|
@ -11,6 +12,7 @@ fs_extra = "1.2"
|
||||||
futures = {version = "0.3", optional = true}
|
futures = {version = "0.3", optional = true}
|
||||||
grass = {version = "0.13", default-features = false}
|
grass = {version = "0.13", default-features = false}
|
||||||
hotwatch = {version = "0.5", optional = true}
|
hotwatch = {version = "0.5", optional = true}
|
||||||
|
include_dir = "0.7"
|
||||||
itertools = "0.13"
|
itertools = "0.13"
|
||||||
lol_html = "2"
|
lol_html = "2"
|
||||||
minifier = {version = "0.3", features = ["html"]}
|
minifier = {version = "0.3", features = ["html"]}
|
||||||
|
|
|
@ -10,10 +10,10 @@ resources:
|
||||||
source_path: blog
|
source_path: blog
|
||||||
output_path_short: blog
|
output_path_short: blog
|
||||||
output_path_long: blog
|
output_path_long: blog
|
||||||
resource_template: blog-post.tera
|
resource_template: blog/blog.tera
|
||||||
resource_list_template: blog-list.tera
|
resource_list_template: blog/list.tera
|
||||||
tag_list_template: basic-link-list.tera
|
tag_list_template: basic-link-list.tera
|
||||||
rss_template: rss/blog-post.tera
|
rss_template: blog/rss.tera
|
||||||
rss_title: webdog blog
|
rss_title: webdog blog
|
||||||
rss_description: feed of recent webdog blog posts.
|
rss_description: feed of recent webdog blog posts.
|
||||||
list_title: blog
|
list_title: blog
|
||||||
|
|
|
@ -46,7 +46,7 @@ pub struct SiteBuilder {
|
||||||
/// The path to the build directory.
|
/// The path to the build directory.
|
||||||
pub build_path: PathBuf,
|
pub build_path: PathBuf,
|
||||||
/// Whether the site is going to be served locally with the dev server.
|
/// Whether the site is going to be served locally with the dev server.
|
||||||
serving: bool,
|
pub serving: bool,
|
||||||
|
|
||||||
/// The resource builders available to the builder.
|
/// The resource builders available to the builder.
|
||||||
pub resource_builders: HashMap<String, ResourceBuilder>,
|
pub resource_builders: HashMap<String, ResourceBuilder>,
|
||||||
|
@ -105,7 +105,7 @@ impl SiteBuilder {
|
||||||
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"),
|
||||||
include_str!("./js/webdog.js"),
|
include_str!("./embedded/js/webdog.js"),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let root_path = self.site.site_path.join(ROOT_PATH);
|
let root_path = self.site.site_path.join(ROOT_PATH);
|
||||||
|
|
1
src/embedded/default_site/.gitignore
vendored
Normal file
1
src/embedded/default_site/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/build
|
7
src/embedded/default_site/pages/404.md
Normal file
7
src/embedded/default_site/pages/404.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
title: Not Found
|
||||||
|
---
|
||||||
|
|
||||||
|
# 404 Not Found
|
||||||
|
|
||||||
|
The page or resource requested does not exist. [Back to home](/)
|
1
src/embedded/default_site/pages/index.md
Normal file
1
src/embedded/default_site/pages/index.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
hello from webdog! awruff!!
|
4
src/embedded/default_site/sass/index.scss
Normal file
4
src/embedded/default_site/sass/index.scss
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
.page {
|
||||||
|
background-color: #bbb;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
29
src/embedded/default_site/templates/base.tera
Normal file
29
src/embedded/default_site/templates/base.tera
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="referrer" content="no-referrer">
|
||||||
|
<link rel="stylesheet" href="/styles/index.css">
|
||||||
|
|
||||||
|
<title>{{ title }}</title>
|
||||||
|
{# header information from webdog #}
|
||||||
|
{{ head | safe }}
|
||||||
|
{# include scripts defined in the page frontmatter #}
|
||||||
|
{% for script in scripts %}
|
||||||
|
<script type="text/javascript" src="{{script}}" defer></script>
|
||||||
|
{% endfor %}
|
||||||
|
{# include styles defined in the page frontmatter #}
|
||||||
|
{% for style in styles %}
|
||||||
|
<link rel="stylesheet" href="/styles/{{style}}">
|
||||||
|
{% endfor %}
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1><a href="/">webdog site</a></h1>
|
||||||
|
<main class="page">
|
||||||
|
{% block content %}{{ page | safe }}{% endblock content %}
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
9
src/embedded/default_site/templates/basic-link-list.tera
Normal file
9
src/embedded/default_site/templates/basic-link-list.tera
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{% extends "base.tera" %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<ul>
|
||||||
|
{% for link in links %}
|
||||||
|
<li><a href="{{link.link}}">{{ link.title }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endblock content %}
|
23
src/embedded/resource-template/list.tera
Normal file
23
src/embedded/resource-template/list.tera
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{% extends "base.tera" %}
|
||||||
|
{% block content %}
|
||||||
|
{% if tag %}
|
||||||
|
<h1>!!RESOURCE_NAME_PLURAL!! tagged {{ tag }}</h1>
|
||||||
|
<p><a href="/!!RESOURCE_TYPE!!/">View all !!RESOURCE_NAME_PLURAL_LOWERCASE!!</a></p>
|
||||||
|
{% else %}
|
||||||
|
<h1>!!RESOURCE_NAME_PLURAL!!</h1>
|
||||||
|
<p><a href="tags">view !!RESOURCE_NAME!! tags</a></p>
|
||||||
|
<p><a href="rss.xml">rss feed</a></p>
|
||||||
|
{% endif %}
|
||||||
|
<h1>Page {{ page }}/{{ page_max }}</h1>
|
||||||
|
{% if previous %}
|
||||||
|
<a href="./{{previous}}">previous page</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if next %}
|
||||||
|
<a href="./{{next}}">next page</a>
|
||||||
|
{% endif %}
|
||||||
|
<div>
|
||||||
|
{% for resource in resources %}
|
||||||
|
<p><a href="/!!RESOURCE_TYPE!!/{{resource.id}}">{{ resource.title }}</a></p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
21
src/embedded/resource-template/resource.tera
Normal file
21
src/embedded/resource-template/resource.tera
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{% extends "base.tera" %}
|
||||||
|
{% block content %}
|
||||||
|
<div>
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<span>published {{ timestamp }}</span>
|
||||||
|
{% if draft %}
|
||||||
|
<h2>DRAFT</h2>
|
||||||
|
{% endif %}
|
||||||
|
<p>{{ desc }}</p>
|
||||||
|
<div>
|
||||||
|
{{ content | safe }}
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<h3>tags</h3>
|
||||||
|
<div>
|
||||||
|
{% for tag in tags %}
|
||||||
|
<a href="/!!RESOURCE_TYPE!!/tag/{{tag}}">{{ tag }}</a>{% if not loop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
1
src/embedded/resource-template/rss.tera
Normal file
1
src/embedded/resource-template/rss.tera
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<div>{{ desc | safe }}</div>
|
|
@ -104,7 +104,7 @@ fn resource_list_outside(
|
||||||
.map(|(id, v)| ResourceTemplateData {
|
.map(|(id, v)| ResourceTemplateData {
|
||||||
resource: v,
|
resource: v,
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
timestamp: v.timestamp,
|
timestamp: v.data().timestamp,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
})?,
|
})?,
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
|
|
||||||
/// Very basic YAML front matter parser.
|
/// Very basic YAML front matter parser.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct FrontMatter<T> {
|
pub struct FrontMatter<T> {
|
||||||
/// The content past the front matter.
|
/// The content past the front matter.
|
||||||
pub content: String,
|
pub content: String,
|
||||||
/// The front matter found, if any.
|
/// The front matter found, if any.
|
||||||
|
#[serde(flatten)]
|
||||||
pub data: Option<T>,
|
pub data: Option<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,3 +57,46 @@ where
|
||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wrapper around `FrontMatter` to only function when the data is present.
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct FrontMatterRequired<T>(FrontMatter<T>);
|
||||||
|
|
||||||
|
impl<T> FrontMatterRequired<T> {
|
||||||
|
/// Gets a reference to the front matter's data.
|
||||||
|
pub fn data(&self) -> &T {
|
||||||
|
self.0.data.as_ref().expect("missing front matter data")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a mutable reference to the front matter's data.
|
||||||
|
pub fn data_mut(&mut self) -> &mut T {
|
||||||
|
self.0.data.as_mut().expect("missing front matter data")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a mutable reference to the front matter's content.
|
||||||
|
pub fn content_mut(&mut self) -> &mut String {
|
||||||
|
&mut self.0.content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> FrontMatterRequired<T>
|
||||||
|
where
|
||||||
|
T: DeserializeOwned,
|
||||||
|
{
|
||||||
|
/// Parses the given input for front matter, failing if missing.
|
||||||
|
pub fn parse(input: String) -> eyre::Result<Self> {
|
||||||
|
let fm = FrontMatter::parse(input)?;
|
||||||
|
if fm.data.is_none() {
|
||||||
|
eyre::bail!("missing frontmatter!");
|
||||||
|
}
|
||||||
|
Ok(Self(fm))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Deref for FrontMatterRequired<T> {
|
||||||
|
type Target = FrontMatter<T>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
47
src/lib.rs
47
src/lib.rs
|
@ -1,8 +1,8 @@
|
||||||
mod builder;
|
mod builder;
|
||||||
mod extras;
|
mod extras;
|
||||||
mod frontmatter;
|
pub mod frontmatter;
|
||||||
mod link_list;
|
mod link_list;
|
||||||
mod resource;
|
pub mod resource;
|
||||||
#[cfg(feature = "serve")]
|
#[cfg(feature = "serve")]
|
||||||
pub mod serving;
|
pub mod serving;
|
||||||
mod util;
|
mod util;
|
||||||
|
@ -16,19 +16,25 @@ use extras::ExtraData;
|
||||||
use eyre::Context;
|
use eyre::Context;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use resource::{EmbedMetadata, ResourceBuilderConfig};
|
use resource::{EmbedMetadata, ResourceBuilderConfig};
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use builder::SiteBuilder;
|
use builder::SiteBuilder;
|
||||||
|
|
||||||
const PAGES_PATH: &str = "pages";
|
/// Source base path for normal site pages.
|
||||||
const TEMPLATES_PATH: &str = "templates";
|
pub const PAGES_PATH: &str = "pages";
|
||||||
const SASS_PATH: &str = "sass";
|
/// Source base path for site templates.
|
||||||
const ROOT_PATH: &str = "root";
|
pub const TEMPLATES_PATH: &str = "templates";
|
||||||
|
/// Source base path for SASS stylesheets.
|
||||||
|
pub const SASS_PATH: &str = "sass";
|
||||||
|
/// Source base path for files which will be copied to the site root.
|
||||||
|
pub const ROOT_PATH: &str = "root";
|
||||||
|
/// Source base path for resources.
|
||||||
|
pub const RESOURCES_PATH: &str = "resources";
|
||||||
|
|
||||||
/// Struct for the site's configuration.
|
/// Struct for the site's configuration.
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct SiteConfig {
|
pub struct SiteConfig {
|
||||||
/// The location the site is at.
|
/// The location the site is at.
|
||||||
pub base_url: Url,
|
pub base_url: Url,
|
||||||
|
@ -53,16 +59,18 @@ pub struct SiteConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SiteConfig {
|
impl SiteConfig {
|
||||||
|
/// The filename for site config files.
|
||||||
|
pub const FILENAME: &str = "config.yaml";
|
||||||
|
|
||||||
/// Creates a new site config from the given title.
|
/// Creates a new site config from the given title.
|
||||||
pub fn new(title: String) -> Self {
|
pub fn new(base_url: Url, cdn_url: Url, title: String) -> Self {
|
||||||
let url: Url = "/".parse().expect("should never fail");
|
|
||||||
Self {
|
Self {
|
||||||
base_url: url.clone(),
|
base_url,
|
||||||
title,
|
title,
|
||||||
description: Default::default(),
|
description: Default::default(),
|
||||||
build: None,
|
build: None,
|
||||||
sass_styles: vec!["index.scss".into()],
|
sass_styles: vec!["index.scss".into()],
|
||||||
cdn_url: url,
|
cdn_url,
|
||||||
code_theme: "base16-ocean.dark".to_string(),
|
code_theme: "base16-ocean.dark".to_string(),
|
||||||
resources: Default::default(),
|
resources: Default::default(),
|
||||||
}
|
}
|
||||||
|
@ -83,6 +91,15 @@ impl SiteConfig {
|
||||||
.ok_or_else(|| eyre::eyre!("missing code theme: {}", self.code_theme))?;
|
.ok_or_else(|| eyre::eyre!("missing code theme: {}", self.code_theme))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper to read the site config from the given path.
|
||||||
|
pub fn read(site_path: &Path) -> eyre::Result<Self> {
|
||||||
|
let config_path = site_path.join(SiteConfig::FILENAME);
|
||||||
|
if !config_path.exists() {
|
||||||
|
eyre::bail!("no site config found!");
|
||||||
|
}
|
||||||
|
Ok(serde_yml::from_str(&std::fs::read_to_string(config_path)?)?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Struct for the front matter in templates. (nothing here yet)
|
/// Struct for the front matter in templates. (nothing here yet)
|
||||||
|
@ -124,11 +141,7 @@ pub struct Site {
|
||||||
impl Site {
|
impl Site {
|
||||||
/// Creates a new site from the given path.
|
/// Creates a new site from the given path.
|
||||||
pub fn new(site_path: &Path) -> eyre::Result<Self> {
|
pub fn new(site_path: &Path) -> eyre::Result<Self> {
|
||||||
let config: SiteConfig = serde_yml::from_str(
|
let config = SiteConfig::read(site_path)?;
|
||||||
&std::fs::read_to_string(site_path.join("config.yaml"))
|
|
||||||
.wrap_err("Failed to read site config")?,
|
|
||||||
)
|
|
||||||
.wrap_err("Failed to parse site config")?;
|
|
||||||
|
|
||||||
let mut page_index = HashMap::new();
|
let mut page_index = HashMap::new();
|
||||||
let pages_path = site_path.join(PAGES_PATH);
|
let pages_path = site_path.join(PAGES_PATH);
|
||||||
|
|
303
src/main.rs
303
src/main.rs
|
@ -1,64 +1,281 @@
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use webdog::Site;
|
use clap::{Parser, Subcommand};
|
||||||
|
use include_dir::{include_dir, Dir};
|
||||||
|
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
|
||||||
|
use url::Url;
|
||||||
|
use webdog::{
|
||||||
|
frontmatter::FrontMatter,
|
||||||
|
resource::{ResourceBuilderConfig, ResourceMetadata},
|
||||||
|
Site, SiteConfig,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(feature = "serve")]
|
/// The default project to use when creating a new one, embedded into the binary.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
static DEFAULT_PROJECT: Dir = include_dir!("$CARGO_MANIFEST_DIR/src/embedded/default_site");
|
||||||
enum Mode {
|
/// The default resource template, embedded into the binary.
|
||||||
Build,
|
static DEFAULT_RESOURCE_TEMPLATES: Dir =
|
||||||
Serve,
|
include_dir!("$CARGO_MANIFEST_DIR/src/embedded/resource-template");
|
||||||
Now,
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
#[command(version, about, long_about = None)]
|
||||||
|
struct Cli {
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: Commands,
|
||||||
|
|
||||||
|
/// The path to the site.
|
||||||
|
#[arg(global = true, long, default_value = ".")]
|
||||||
|
site_path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
enum Commands {
|
||||||
|
/// Create a new webdog site.
|
||||||
|
Create {
|
||||||
|
/// The site's base URL.
|
||||||
|
base_url: Url,
|
||||||
|
/// The site's title.
|
||||||
|
title: String,
|
||||||
|
/// The site's CDN URL. (defaults to the base URL)
|
||||||
|
#[arg(long)]
|
||||||
|
cdn_url: Option<Url>,
|
||||||
|
},
|
||||||
|
/// Builds the site.
|
||||||
|
Build {},
|
||||||
|
/// Serves the site for locally viewing edits made before publishing.
|
||||||
|
#[cfg(feature = "serve")]
|
||||||
|
Serve {
|
||||||
|
/// The IP address to bind to.
|
||||||
|
#[arg(long, default_value = "127.0.0.1")]
|
||||||
|
ip: String,
|
||||||
|
/// The port to bind to.
|
||||||
|
#[arg(short, long, default_value = "8080")]
|
||||||
|
port: u16,
|
||||||
|
},
|
||||||
|
/// Helper to get the current timestamp.
|
||||||
|
Now,
|
||||||
|
/// For dealing with site resources.
|
||||||
|
Resource {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
command: ResourceCommands,
|
||||||
|
},
|
||||||
|
/// Creates a new resource of the given type.
|
||||||
|
New {
|
||||||
|
/// The type of resource to create.
|
||||||
|
resource_type: String,
|
||||||
|
/// The resource's ID.
|
||||||
|
id: String,
|
||||||
|
/// The resource's title.
|
||||||
|
title: String,
|
||||||
|
/// The resource's tags.
|
||||||
|
#[arg(short, long = "tag")]
|
||||||
|
tags: Vec<String>,
|
||||||
|
/// The resource's description.
|
||||||
|
#[arg(short, long)]
|
||||||
|
description: Option<String>,
|
||||||
|
/// Whether to skip setting the resource as a draft or not.
|
||||||
|
#[arg(long, default_value = "false")]
|
||||||
|
skip_draft: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
enum ResourceCommands {
|
||||||
|
/// Creates a new resource type.
|
||||||
|
Create {
|
||||||
|
/// The resource type's ID.
|
||||||
|
id: String,
|
||||||
|
/// The name of the resource type to create.
|
||||||
|
name: String,
|
||||||
|
/// The name of the resource type, but plural.
|
||||||
|
plural: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serve")]
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> eyre::Result<()> {
|
async fn main() -> eyre::Result<()> {
|
||||||
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
|
|
||||||
|
|
||||||
#[cfg(feature = "color-eyre")]
|
#[cfg(feature = "color-eyre")]
|
||||||
color_eyre::install()?;
|
color_eyre::install()?;
|
||||||
|
|
||||||
let site = Site::new(&Path::new("site").canonicalize()?)?;
|
let cli = Cli::parse();
|
||||||
|
|
||||||
let mut mode = Mode::Build;
|
let site = || -> eyre::Result<Site> { Site::new(&Path::new(&cli.site_path).canonicalize()?) };
|
||||||
for arg in std::env::args() {
|
|
||||||
if arg == "serve" {
|
|
||||||
mode = Mode::Serve;
|
|
||||||
break;
|
|
||||||
} else if arg == "now" {
|
|
||||||
mode = Mode::Now;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match mode {
|
match cli.command {
|
||||||
Mode::Build => {
|
Commands::Create {
|
||||||
build(site)?;
|
base_url,
|
||||||
|
cdn_url,
|
||||||
|
title,
|
||||||
|
} => {
|
||||||
|
if cli.site_path.exists() {
|
||||||
|
eprintln!("content exists in the given path! canceling!");
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
Mode::Serve => site.serve("127.0.0.1:8080").await?,
|
std::fs::create_dir_all(&cli.site_path)?;
|
||||||
Mode::Now => {
|
let config = SiteConfig::new(base_url.clone(), cdn_url.unwrap_or(base_url), title);
|
||||||
let time = OffsetDateTime::now_utc();
|
std::fs::write(
|
||||||
|
cli.site_path.join(SiteConfig::FILENAME),
|
||||||
|
serde_yml::to_string(&config)?,
|
||||||
|
)?;
|
||||||
|
DEFAULT_PROJECT.extract(&cli.site_path)?;
|
||||||
|
std::fs::create_dir(cli.site_path.join(webdog::ROOT_PATH))?;
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"Base site created at {:?}! Ready for editing, woof!",
|
||||||
time.format(&Rfc3339)
|
cli.site_path
|
||||||
.expect("failed to format the current time")
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Commands::Build {} => {
|
||||||
#[cfg(not(feature = "serve"))]
|
|
||||||
fn main() -> eyre::Result<()> {
|
|
||||||
let site = Site::new(&Path::new("site").canonicalize()?)?;
|
|
||||||
build(site)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build(site: Site) -> eyre::Result<()> {
|
|
||||||
println!("Building site...");
|
println!("Building site...");
|
||||||
let now = std::time::Instant::now();
|
let now = std::time::Instant::now();
|
||||||
site.build_once()?;
|
site()?.build_once()?;
|
||||||
println!("Build completed in {:?}", now.elapsed());
|
println!("Build completed in {:?}", now.elapsed());
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
|
#[cfg(feature = "serve")]
|
||||||
|
Commands::Serve { ip, port } => {
|
||||||
|
let site = site()?;
|
||||||
|
site.serve(&format!("{}:{}", ip, port)).await
|
||||||
|
}
|
||||||
|
Commands::Now => {
|
||||||
|
let time = OffsetDateTime::now_utc();
|
||||||
|
println!("{}", time.format(&Rfc3339)?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Commands::Resource { command } => match command {
|
||||||
|
ResourceCommands::Create { id, name, plural } => {
|
||||||
|
let config_path = cli.site_path.join(SiteConfig::FILENAME);
|
||||||
|
let mut config = SiteConfig::read(&cli.site_path)?;
|
||||||
|
if config.resources.contains_key(&id) {
|
||||||
|
eprintln!("resource type {id} already exists, canceling");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let resource_template_path = cli.site_path.join(webdog::TEMPLATES_PATH).join(&id);
|
||||||
|
if resource_template_path.exists() {
|
||||||
|
eprintln!(
|
||||||
|
"path for resource already exists at {resource_template_path:?}, canceling"
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
std::fs::create_dir_all(&resource_template_path)?;
|
||||||
|
for file in DEFAULT_RESOURCE_TEMPLATES.files() {
|
||||||
|
let resource_path = resource_template_path.join(file.path());
|
||||||
|
if let Some(contents) = file.contents_utf8() {
|
||||||
|
let mut contents = contents.to_owned();
|
||||||
|
contents = contents.replace("!!RESOURCE_TYPE!!", &id);
|
||||||
|
contents = contents.replace("!!RESOURCE_NAME!!", &name);
|
||||||
|
contents =
|
||||||
|
contents.replace("!!RESOURCE_NAME_LOWERCASE!!", &name.to_lowercase());
|
||||||
|
contents = contents.replace("!!RESOURCE_NAME_PLURAL!!", &plural);
|
||||||
|
contents = contents
|
||||||
|
.replace("!!RESOURCE_NAME_PLURAL_LOWERCASE!!", &plural.to_lowercase());
|
||||||
|
std::fs::write(resource_path, contents)?;
|
||||||
|
} else {
|
||||||
|
std::fs::write(resource_path, file.contents())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let resource_config = ResourceBuilderConfig {
|
||||||
|
source_path: id.clone(),
|
||||||
|
output_path_short: id.clone(),
|
||||||
|
output_path_long: id.clone(),
|
||||||
|
resource_template: format!("{id}/resource.tera"),
|
||||||
|
resource_list_template: format!("{id}/list.tera"),
|
||||||
|
tag_list_template: "basic-link-list.tera".to_string(),
|
||||||
|
rss_template: format!("{id}/rss.tera"),
|
||||||
|
rss_title: id.clone(),
|
||||||
|
rss_description: Default::default(),
|
||||||
|
list_title: name.clone(),
|
||||||
|
tag_list_title: format!("{name} tags"),
|
||||||
|
resource_name_plural: plural,
|
||||||
|
resources_per_page: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
config.resources.insert(id.clone(), resource_config);
|
||||||
|
|
||||||
|
std::fs::write(config_path, serde_yml::to_string(&config)?)?;
|
||||||
|
|
||||||
|
let resource_path = cli.site_path.join(webdog::RESOURCES_PATH).join(&id);
|
||||||
|
std::fs::create_dir_all(&resource_path)?;
|
||||||
|
|
||||||
|
create_resource(
|
||||||
|
&resource_path.join("first.md"),
|
||||||
|
&ResourceMetadata {
|
||||||
|
title: format!("First {name}"),
|
||||||
|
timestamp: OffsetDateTime::now_utc(),
|
||||||
|
tags: vec!["first".to_string()],
|
||||||
|
cdn_file: None,
|
||||||
|
desc: Some(format!("This is the first {name} :)")),
|
||||||
|
inner: serde_yml::Value::Null,
|
||||||
|
draft: true,
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
println!("Created the new resource type {id}! The first resource of this time is available at {:?}.", resource_path);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Commands::New {
|
||||||
|
resource_type,
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
tags,
|
||||||
|
description,
|
||||||
|
skip_draft,
|
||||||
|
} => {
|
||||||
|
let config = SiteConfig::read(&cli.site_path)?;
|
||||||
|
if let Some(resource) = config.resources.get(&resource_type) {
|
||||||
|
let resource_path = cli
|
||||||
|
.site_path
|
||||||
|
.join(webdog::RESOURCES_PATH)
|
||||||
|
.join(&resource.source_path)
|
||||||
|
.join(&id)
|
||||||
|
.with_extension("md");
|
||||||
|
|
||||||
|
if resource_path.exists() {
|
||||||
|
eprintln!(
|
||||||
|
"A {resource_type} resource of the ID {id} already exists, canceling!"
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
create_resource(
|
||||||
|
&resource_path,
|
||||||
|
&ResourceMetadata {
|
||||||
|
title,
|
||||||
|
timestamp: OffsetDateTime::now_utc(),
|
||||||
|
tags,
|
||||||
|
cdn_file: None,
|
||||||
|
desc: description,
|
||||||
|
inner: serde_yml::Value::Null,
|
||||||
|
draft: !skip_draft,
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Created the new {resource_type} resource {id}! Available at {:?}",
|
||||||
|
resource_path
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
eprintln!("no resource of type {resource_type}, canceling");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new resource from the given metadata.
|
||||||
|
fn create_resource(resource_path: &Path, metadata: &ResourceMetadata) -> eyre::Result<()> {
|
||||||
|
std::fs::write(
|
||||||
|
resource_path,
|
||||||
|
FrontMatter {
|
||||||
|
content: "hello world :)".to_string(),
|
||||||
|
data: Some(metadata),
|
||||||
|
}
|
||||||
|
.format()?,
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,9 @@ use rss::{validation::Validate, ChannelBuilder, ItemBuilder};
|
||||||
use serde::{Deserialize, Serialize, Serializer};
|
use serde::{Deserialize, Serialize, Serializer};
|
||||||
use time::{format_description::well_known::Rfc2822, OffsetDateTime};
|
use time::{format_description::well_known::Rfc2822, OffsetDateTime};
|
||||||
|
|
||||||
use crate::{builder::SiteBuilder, frontmatter::FrontMatter, link_list::Link, PageMetadata};
|
use crate::{
|
||||||
|
builder::SiteBuilder, frontmatter::FrontMatterRequired, link_list::Link, PageMetadata,
|
||||||
/// Source base path for resources.
|
};
|
||||||
pub const RESOURCES_PATH: &str = "resources";
|
|
||||||
|
|
||||||
/// Metadata for resources.
|
/// Metadata for resources.
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
@ -35,16 +34,13 @@ pub struct ResourceMetadata {
|
||||||
/// 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,
|
||||||
/// The resource's content. Defaults to nothing until loaded in another step.
|
|
||||||
#[serde(default)]
|
|
||||||
pub content: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct ResourceTemplateData<'r> {
|
pub struct ResourceTemplateData<'r> {
|
||||||
/// The resource's metadata.
|
/// The resource's metadata.
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub resource: &'r ResourceMetadata,
|
pub resource: &'r FrontMatterRequired<ResourceMetadata>,
|
||||||
/// The resource's ID.
|
/// The resource's ID.
|
||||||
pub id: String,
|
pub id: String,
|
||||||
/// The resource's timestamp. Duplicated to change serialization method.
|
/// The resource's timestamp. Duplicated to change serialization method.
|
||||||
|
@ -129,7 +125,7 @@ struct ResourceListTemplateData<'r> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Config for the resource builder.
|
/// Config for the resource builder.
|
||||||
#[derive(Debug, Clone, Default, Deserialize)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||||
pub struct ResourceBuilderConfig {
|
pub struct ResourceBuilderConfig {
|
||||||
/// Path to where the resources should be loaded from.
|
/// Path to where the resources should be loaded from.
|
||||||
pub source_path: String,
|
pub source_path: String,
|
||||||
|
@ -165,7 +161,7 @@ pub struct ResourceBuilder {
|
||||||
/// The builder's config.
|
/// The builder's config.
|
||||||
pub config: ResourceBuilderConfig,
|
pub config: ResourceBuilderConfig,
|
||||||
/// The currently loaded resource metadata.
|
/// The currently loaded resource metadata.
|
||||||
pub loaded_metadata: Vec<(String, ResourceMetadata)>,
|
pub loaded_metadata: Vec<(String, FrontMatterRequired<ResourceMetadata>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResourceBuilder {
|
impl ResourceBuilder {
|
||||||
|
@ -187,26 +183,27 @@ impl ResourceBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads resource metadata from the given path.
|
/// Loads resource metadata from the given path.
|
||||||
fn load(builder: &SiteBuilder, path: &Path) -> eyre::Result<(String, ResourceMetadata)> {
|
fn load(
|
||||||
|
builder: &SiteBuilder,
|
||||||
|
path: &Path,
|
||||||
|
) -> eyre::Result<(String, FrontMatterRequired<ResourceMetadata>)> {
|
||||||
let id = Self::get_id(path);
|
let id = Self::get_id(path);
|
||||||
|
|
||||||
let input = std::fs::read_to_string(path)?;
|
let input = std::fs::read_to_string(path)?;
|
||||||
let page = FrontMatter::<ResourceMetadata>::parse(input)
|
let mut page = FrontMatterRequired::<ResourceMetadata>::parse(input)
|
||||||
.wrap_err_with(|| eyre::eyre!("Failed to parse resource front matter"))?;
|
.wrap_err_with(|| eyre::eyre!("Failed to parse resource front matter"))?;
|
||||||
|
|
||||||
let parser = Parser::new_ext(&page.content, Options::all());
|
let parser = Parser::new_ext(&page.content, Options::all());
|
||||||
let mut html = String::new();
|
let mut html = String::new();
|
||||||
pulldown_cmark::html::push_html(&mut html, parser);
|
pulldown_cmark::html::push_html(&mut html, parser);
|
||||||
|
*page.content_mut() = html;
|
||||||
|
|
||||||
let mut data = page
|
let data = page.data_mut();
|
||||||
.data
|
if let Some(cdn_file) = &data.cdn_file {
|
||||||
.ok_or_else(|| eyre::eyre!("missing front matter for file at {path:?}"))?;
|
data.cdn_file = Some(builder.site.config.cdn_url(cdn_file)?.to_string());
|
||||||
data.content = html;
|
|
||||||
if let Some(cdn_file) = data.cdn_file {
|
|
||||||
data.cdn_file = Some(builder.site.config.cdn_url(&cdn_file)?.to_string());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((id, data))
|
Ok((id, page))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads all resource metadata from the given config.
|
/// Loads all resource metadata from the given config.
|
||||||
|
@ -216,20 +213,26 @@ impl ResourceBuilder {
|
||||||
for e in builder
|
for e in builder
|
||||||
.site
|
.site
|
||||||
.site_path
|
.site_path
|
||||||
.join(RESOURCES_PATH)
|
.join(crate::RESOURCES_PATH)
|
||||||
.join(&self.config.source_path)
|
.join(&self.config.source_path)
|
||||||
.read_dir()?
|
.read_dir()?
|
||||||
{
|
{
|
||||||
let p = e?.path();
|
let p = e?.path();
|
||||||
if let Some("md") = p.extension().and_then(|e| e.to_str()) {
|
if let Some("md") = p.extension().and_then(|e| e.to_str()) {
|
||||||
let (id, metadata) = Self::load(builder, &p)?;
|
let (id, metadata) = Self::load(builder, &p)?;
|
||||||
if cfg!(not(debug_assertions)) && metadata.draft {
|
if !builder.serving && metadata.data.as_ref().expect("should never fail").draft {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
lmd.push((id, metadata));
|
lmd.push((id, metadata));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lmd.sort_by(|a, b| b.1.timestamp.cmp(&a.1.timestamp));
|
lmd.sort_by(|a, b| {
|
||||||
|
b.1.data
|
||||||
|
.as_ref()
|
||||||
|
.expect("should never fail")
|
||||||
|
.timestamp
|
||||||
|
.cmp(&a.1.data.as_ref().expect("should never fail").timestamp)
|
||||||
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,19 +249,20 @@ impl ResourceBuilder {
|
||||||
&self,
|
&self,
|
||||||
builder: &SiteBuilder,
|
builder: &SiteBuilder,
|
||||||
id: String,
|
id: String,
|
||||||
resource: &ResourceMetadata,
|
resource: &FrontMatterRequired<ResourceMetadata>,
|
||||||
) -> eyre::Result<()> {
|
) -> eyre::Result<()> {
|
||||||
let out_path = self.build_path(&builder.build_path, &id);
|
let out_path = self.build_path(&builder.build_path, &id);
|
||||||
|
|
||||||
|
let data = resource.data();
|
||||||
let out = builder.build_page_raw(
|
let out = builder.build_page_raw(
|
||||||
PageMetadata {
|
PageMetadata {
|
||||||
template: Some(self.config.resource_template.clone()),
|
template: Some(self.config.resource_template.clone()),
|
||||||
title: Some(resource.title.clone()),
|
title: Some(data.title.clone()),
|
||||||
embed: Some(EmbedMetadata {
|
embed: Some(EmbedMetadata {
|
||||||
title: resource.title.clone(),
|
title: data.title.clone(),
|
||||||
site_name: builder.site.config.title.clone(),
|
site_name: builder.site.config.title.clone(),
|
||||||
description: resource.desc.clone(),
|
description: data.desc.clone(),
|
||||||
image: if let Some(cdn_file) = &resource.cdn_file {
|
image: if let Some(cdn_file) = &data.cdn_file {
|
||||||
Some(builder.site.config.cdn_url(cdn_file)?.to_string())
|
Some(builder.site.config.cdn_url(cdn_file)?.to_string())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -273,7 +277,7 @@ impl ResourceBuilder {
|
||||||
ResourceTemplateData {
|
ResourceTemplateData {
|
||||||
resource,
|
resource,
|
||||||
id,
|
id,
|
||||||
timestamp: resource.timestamp,
|
timestamp: resource.data.as_ref().expect("should never fail").timestamp,
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
std::fs::write(out_path, out)?;
|
std::fs::write(out_path, out)?;
|
||||||
|
@ -303,7 +307,7 @@ impl ResourceBuilder {
|
||||||
data.push(ResourceTemplateData {
|
data.push(ResourceTemplateData {
|
||||||
resource,
|
resource,
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
timestamp: resource.timestamp,
|
timestamp: resource.data().timestamp,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,7 +372,7 @@ impl ResourceBuilder {
|
||||||
// Build resource lists by tag
|
// Build resource lists by tag
|
||||||
let mut tags: BTreeMap<String, Vec<&ResourceTemplateData>> = BTreeMap::new();
|
let mut tags: BTreeMap<String, Vec<&ResourceTemplateData>> = BTreeMap::new();
|
||||||
for resource in &data {
|
for resource in &data {
|
||||||
for tag in resource.resource.tags.iter().cloned() {
|
for tag in resource.resource.data().tags.iter().cloned() {
|
||||||
tags.entry(tag).or_default().push(resource);
|
tags.entry(tag).or_default().push(resource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -416,7 +420,7 @@ impl ResourceBuilder {
|
||||||
for resource in data {
|
for resource in data {
|
||||||
items.push(
|
items.push(
|
||||||
ItemBuilder::default()
|
ItemBuilder::default()
|
||||||
.title(Some(resource.resource.title.to_owned()))
|
.title(Some(resource.resource.data().title.to_owned()))
|
||||||
.link(Some(
|
.link(Some(
|
||||||
builder
|
builder
|
||||||
.site
|
.site
|
||||||
|
@ -428,7 +432,7 @@ impl ResourceBuilder {
|
||||||
))?
|
))?
|
||||||
.to_string(),
|
.to_string(),
|
||||||
))
|
))
|
||||||
.description(resource.resource.desc.clone())
|
.description(resource.resource.data().desc.clone())
|
||||||
.pub_date(Some(resource.timestamp.format(&Rfc2822)?))
|
.pub_date(Some(resource.timestamp.format(&Rfc2822)?))
|
||||||
.content(Some(builder.tera.render(
|
.content(Some(builder.tera.render(
|
||||||
&self.config.rss_template,
|
&self.config.rss_template,
|
||||||
|
|
|
@ -19,7 +19,7 @@ use warp::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
resource::RESOURCES_PATH, Site, SiteBuilder, PAGES_PATH, ROOT_PATH, SASS_PATH, TEMPLATES_PATH,
|
Site, SiteBuilder, SiteConfig, PAGES_PATH, RESOURCES_PATH, ROOT_PATH, SASS_PATH, TEMPLATES_PATH,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Helper to get the "name" of a path.
|
/// Helper to get the "name" of a path.
|
||||||
|
@ -94,7 +94,7 @@ fn create(
|
||||||
builder.site.build_all_pages(builder)?;
|
builder.site.build_all_pages(builder)?;
|
||||||
builder.build_all_resources()?;
|
builder.build_all_resources()?;
|
||||||
}
|
}
|
||||||
} else if relative_path.display().to_string() == "config.yaml" {
|
} else if relative_path.display().to_string() == SiteConfig::FILENAME {
|
||||||
let new_config = serde_yml::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()?;
|
||||||
|
@ -277,8 +277,9 @@ impl Site {
|
||||||
.expect("Failed to decode URL");
|
.expect("Failed to decode URL");
|
||||||
|
|
||||||
if p == "_dev.js" {
|
if p == "_dev.js" {
|
||||||
let res =
|
let res = Response::new(
|
||||||
Response::new(include_str!("./js/refresh_websocket.js").into());
|
include_str!("./embedded/js/refresh_websocket.js").into(),
|
||||||
|
);
|
||||||
return Ok(res);
|
return Ok(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue