Compare commits
No commits in common. "foundation" and "main" have entirely different histories.
foundation
...
main
|
@ -1,2 +1 @@
|
||||||
target/
|
target/
|
||||||
build/
|
|
|
@ -180,9 +180,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.139"
|
version = "0.2.138"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
|
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
|
@ -208,15 +208,13 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"toml",
|
|
||||||
"walkdir",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest"
|
name = "pest"
|
||||||
version = "2.5.2"
|
version = "2.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0f6e86fb9e7026527a0d46bc308b841d73170ef8f443e1807f6ef88526a816d4"
|
checksum = "cc8bed3549e0f9b0a2a78bf7c0018237a2cdf085eecbbc048e52612438e4e9d0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"ucd-trie",
|
"ucd-trie",
|
||||||
|
@ -224,9 +222,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest_derive"
|
name = "pest_derive"
|
||||||
version = "2.5.2"
|
version = "2.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "96504449aa860c8dcde14f9fba5c58dc6658688ca1fe363589d6327b8662c603"
|
checksum = "cdc078600d06ff90d4ed238f0119d84ab5d43dbaad278b0e33a8820293b32344"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pest",
|
"pest",
|
||||||
"pest_generator",
|
"pest_generator",
|
||||||
|
@ -234,9 +232,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest_generator"
|
name = "pest_generator"
|
||||||
version = "2.5.2"
|
version = "2.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "798e0220d1111ae63d66cb66a5dcb3fc2d986d520b98e49e1852bfdb11d7c5e7"
|
checksum = "28a1af60b1c4148bb269006a750cff8e2ea36aff34d2d96cf7be0b14d1bed23c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pest",
|
"pest",
|
||||||
"pest_meta",
|
"pest_meta",
|
||||||
|
@ -247,9 +245,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest_meta"
|
name = "pest_meta"
|
||||||
version = "2.5.2"
|
version = "2.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "984298b75898e30a843e278a9f2452c31e349a073a0ce6fd950a12a74464e065"
|
checksum = "fec8605d59fc2ae0c6c1aefc0c7c7a9769732017c0ce07f7a9cfffa7b4404f20"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pest",
|
"pest",
|
||||||
|
@ -289,15 +287,6 @@ version = "1.0.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
|
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "same-file"
|
|
||||||
version = "1.0.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
|
||||||
dependencies = [
|
|
||||||
"winapi-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
@ -392,15 +381,6 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml"
|
|
||||||
version = "0.5.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.16.0"
|
version = "1.16.0"
|
||||||
|
@ -424,45 +404,3 @@ name = "version_check"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "walkdir"
|
|
||||||
version = "2.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
|
|
||||||
dependencies = [
|
|
||||||
"same-file",
|
|
||||||
"winapi",
|
|
||||||
"winapi-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi"
|
|
||||||
version = "0.3.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
|
||||||
dependencies = [
|
|
||||||
"winapi-i686-pc-windows-gnu",
|
|
||||||
"winapi-x86_64-pc-windows-gnu",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-i686-pc-windows-gnu"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-util"
|
|
||||||
version = "0.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
|
||||||
dependencies = [
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-x86_64-pc-windows-gnu"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
|
||||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -3,18 +3,22 @@ name = "org-static"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
emacs = "0.18.0"
|
emacs = "0.18.0"
|
||||||
|
|
||||||
serde = { version = "1.0.151", features = ["derive"] }
|
serde = { version = "1.0.151", features = ["derive"] }
|
||||||
handlebars = "4.3.6"
|
handlebars = "4.3.6"
|
||||||
serde_json = "1.0.91"
|
serde_json = "1.0.91"
|
||||||
walkdir = "2.3.2"
|
|
||||||
toml = "0.5.10"
|
[dev-dependencies]
|
||||||
|
#emacs-rs-module = { version = "0.13.0" }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
use org_static::publish;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
match publish("examples/basic".into()) {
|
|
||||||
Ok(()) => println!("Complete"),
|
|
||||||
Err(e) => println!("Error: {}", e),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
#+title: A test Post
|
|
||||||
#+filetags: :test:
|
|
||||||
|
|
||||||
|
|
||||||
* Article Heading
|
|
||||||
|
|
||||||
Heading
|
|
||||||
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
|
||||||
(org-static-publish-current-project)
|
|
||||||
(org-static-publish "/media/joe/extradrive1/Projects/fivesigma/org-static/examples/basic/articles/")
|
|
||||||
#+end_src
|
|
|
@ -1,2 +0,0 @@
|
||||||
[site]
|
|
||||||
title = "Basic Example Site"
|
|
|
@ -1,5 +0,0 @@
|
||||||
<div class="menu">Some Menu</div>
|
|
||||||
<div>title: {{ article.title }}</div>
|
|
||||||
<Article>
|
|
||||||
{{{content}}}
|
|
||||||
</Article>
|
|
10
readme.org
10
readme.org
|
@ -1,10 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
|
||||||
(progn
|
|
||||||
(add-to-list 'load-path "/media/joe/extradrive1/Projects/fivesigma/org-static/target/debug")
|
|
||||||
(require 'org-static))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
|
|
||||||
[[file:examples/basic/articles/a-test-pos.org]]
|
|
|
@ -1,26 +1,15 @@
|
||||||
|
use crate::{error::Result, site::Site, template::Template};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::{collections::HashSet, path::PathBuf};
|
use serde_json::json;
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use crate::config::Config;
|
#[derive(Serialize, Default)]
|
||||||
|
|
||||||
#[derive(Serialize, Default, Clone)]
|
|
||||||
pub struct Article {
|
pub struct Article {
|
||||||
pub title: String,
|
filename: String,
|
||||||
pub date: String,
|
date: String,
|
||||||
pub author: String,
|
author: String,
|
||||||
pub slug: String,
|
properties: HashMap<String, String>,
|
||||||
pub file_path: PathBuf,
|
tags: HashSet<String>,
|
||||||
pub tags: HashSet<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Article {
|
|
||||||
pub fn get_build_path(&self, config: &Config) -> PathBuf {
|
|
||||||
config
|
|
||||||
.get_build_path()
|
|
||||||
.join("articles")
|
|
||||||
.join(&self.slug)
|
|
||||||
.join("index.html")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Default)]
|
#[derive(Serialize, Default)]
|
||||||
|
@ -30,7 +19,65 @@ pub struct ArticleContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArticleContext {
|
impl ArticleContext {
|
||||||
pub fn new(article: Article, content: String) -> Self {
|
pub fn load(article: Article) -> Result<Self> {
|
||||||
Self { article, content }
|
Ok(Self {
|
||||||
|
article,
|
||||||
|
content: String::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn load_string(article: Article, content: &str) -> Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
article,
|
||||||
|
content: content.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_article(
|
||||||
|
site: &Site,
|
||||||
|
article: &ArticleContext,
|
||||||
|
template: &Template,
|
||||||
|
) -> Result<String> {
|
||||||
|
let ctx = json!({
|
||||||
|
"site": site,
|
||||||
|
"data": article,
|
||||||
|
});
|
||||||
|
let res = template.generate(&ctx)?;
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::template::Template;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn fixture_article() -> Article {
|
||||||
|
Article {
|
||||||
|
filename: "test.org".to_string(),
|
||||||
|
date: "".to_string(),
|
||||||
|
author: "Bob Bobberson".to_string(),
|
||||||
|
properties: HashMap::new(),
|
||||||
|
tags: vec!["article", "new", "cool"]
|
||||||
|
.into_iter()
|
||||||
|
.map(String::from)
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_generate() {
|
||||||
|
let article_ctx =
|
||||||
|
ArticleContext::load_string(fixture_article(), "ten animals i slam in a net")
|
||||||
|
.expect("couldnt build article");
|
||||||
|
let template =
|
||||||
|
Template::new("content: {{{data.content}}}, site: {{site.title}}".to_string());
|
||||||
|
let site = crate::site::tests::fixture_site();
|
||||||
|
let res =
|
||||||
|
generate_article(&site, &article_ctx, &template).expect("couldnt gnenerate template");
|
||||||
|
|
||||||
|
assert_eq!(res, "content: ten animals i slam in a net, site: Test Site");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
112
src/config.rs
112
src/config.rs
|
@ -1,112 +0,0 @@
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::{error::Error, site::Site};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
#[serde(default)]
|
|
||||||
pub struct Config {
|
|
||||||
pub project_root: PathBuf,
|
|
||||||
pub build_path: PathBuf,
|
|
||||||
pub articles_path: PathBuf,
|
|
||||||
pub theme_path: PathBuf,
|
|
||||||
pub static_path: PathBuf,
|
|
||||||
pub partials_path: PathBuf,
|
|
||||||
pub site: Site,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Config {
|
|
||||||
fn default() -> Self {
|
|
||||||
Config {
|
|
||||||
site: Site::default(),
|
|
||||||
project_root: ".".into(),
|
|
||||||
build_path: "build".into(),
|
|
||||||
articles_path: "articles".into(),
|
|
||||||
theme_path: "theme".into(),
|
|
||||||
static_path: "static".into(),
|
|
||||||
partials_path: "partials".into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
pub fn find(path: PathBuf) -> crate::error::Result<Self> {
|
|
||||||
if let Some(p) = find_config(path) {
|
|
||||||
let content = std::fs::read_to_string(&p)?;
|
|
||||||
let mut cfg: Config = toml::from_str(&content)?;
|
|
||||||
cfg.project_root = p
|
|
||||||
.canonicalize()?
|
|
||||||
.parent()
|
|
||||||
.ok_or_else(|| Error::new("Couldnt find project root"))?
|
|
||||||
.to_path_buf();
|
|
||||||
Ok(cfg)
|
|
||||||
} else {
|
|
||||||
Err(Error::new("Config file not found"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_build_path(&self) -> PathBuf {
|
|
||||||
if self.build_path.is_relative() {
|
|
||||||
PathBuf::from(&self.project_root).join(&self.build_path)
|
|
||||||
} else {
|
|
||||||
self.build_path.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_articles_path(&self) -> PathBuf {
|
|
||||||
if self.articles_path.is_relative() {
|
|
||||||
PathBuf::from(&self.project_root).join(&self.articles_path)
|
|
||||||
} else {
|
|
||||||
self.articles_path.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_theme_path(&self) -> PathBuf {
|
|
||||||
if self.theme_path.is_relative() {
|
|
||||||
PathBuf::from(&self.project_root).join(&self.theme_path)
|
|
||||||
} else {
|
|
||||||
self.theme_path.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_static_path(&self) -> PathBuf {
|
|
||||||
if self.static_path.is_relative() {
|
|
||||||
PathBuf::from(&self.project_root).join(&self.static_path)
|
|
||||||
} else {
|
|
||||||
self.static_path.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_partials_path(&self) -> PathBuf {
|
|
||||||
if self.partials_path.is_relative() {
|
|
||||||
PathBuf::from(&self.project_root).join(&self.partials_path)
|
|
||||||
} else {
|
|
||||||
self.partials_path.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_config(mut path: PathBuf) -> Option<PathBuf> {
|
|
||||||
let config_path = path.join("orgstatic.toml");
|
|
||||||
if config_path.is_file() {
|
|
||||||
Some(config_path)
|
|
||||||
} else if path.pop() {
|
|
||||||
find_config(path)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_find_config() {
|
|
||||||
let path = PathBuf::from("examples/basic/articles/")
|
|
||||||
.canonicalize()
|
|
||||||
.unwrap();
|
|
||||||
Config::find(path).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
(require 'liborg_static)
|
|
||||||
|
|
||||||
(defun org-static-file-info (filename)
|
|
||||||
(vector (nth 0 (cdr (nth 0 (org-collect-keywords '("title" "")))))
|
|
||||||
(nth 0 (cdr (nth 0 (org-collect-keywords '("author" "")))))
|
|
||||||
(nth 0 (cdr (nth 0 (org-collect-keywords '("date" "")))))
|
|
||||||
(nth 0 (cdr (nth 0 (org-collect-keywords '("slug" "")))))
|
|
||||||
(nth 0 (cdr (nth 0 (org-collect-keywords '("filetags" "")))))))
|
|
||||||
|
|
||||||
|
|
||||||
(defun org-static-file-contents (filename)
|
|
||||||
(org-export-string-as (f-read-text filename 'utf-8) 'html t))
|
|
||||||
|
|
||||||
|
|
||||||
(defun org-static-publish-current-project ()
|
|
||||||
(org-static-publish (file-name-directory (buffer-file-name))))
|
|
||||||
|
|
||||||
(provide 'org-static)
|
|
||||||
|
|
||||||
|
|
14
src/error.rs
14
src/error.rs
|
@ -11,6 +11,8 @@ impl std::fmt::Display for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
pub fn new(message: &str) -> Self {
|
pub fn new(message: &str) -> Self {
|
||||||
Error {
|
Error {
|
||||||
|
@ -19,16 +21,18 @@ impl Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: std::error::Error> From<T> for Error {
|
impl From<handlebars::TemplateError> for Error {
|
||||||
fn from(source: T) -> Self {
|
fn from(source: handlebars::TemplateError) -> Self {
|
||||||
Self {
|
Self {
|
||||||
message: source.to_string(),
|
message: source.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Error> for emacs::Error {
|
impl From<handlebars::RenderError> for Error {
|
||||||
fn from(source: Error) -> Self {
|
fn from(source: handlebars::RenderError) -> Self {
|
||||||
emacs::Error::msg(source)
|
Self {
|
||||||
|
message: source.to_string(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
112
src/lib.rs
112
src/lib.rs
|
@ -1,117 +1,25 @@
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
mod article;
|
mod article;
|
||||||
mod config;
|
|
||||||
mod error;
|
mod error;
|
||||||
mod site;
|
mod site;
|
||||||
mod template;
|
mod template;
|
||||||
|
|
||||||
use article::{Article, ArticleContext};
|
use emacs::{defun, Env, Result, Value};
|
||||||
use std::path::PathBuf;
|
|
||||||
use template::TemplateEngine;
|
|
||||||
use walkdir::WalkDir;
|
|
||||||
|
|
||||||
use emacs::{defun, Env, IntoLisp, Result, Vector};
|
|
||||||
|
|
||||||
emacs::plugin_is_GPL_compatible!();
|
emacs::plugin_is_GPL_compatible!();
|
||||||
|
|
||||||
#[emacs::module(name = "liborg_static", defun_prefix = "org-static")]
|
#[emacs::module(name = "org-static")]
|
||||||
fn init(e: &Env) -> Result<()> {
|
fn init(_: &Env) -> Result<()> {
|
||||||
e.message("orgstatic loaded")?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_articles(env: &Env, config: &config::Config) -> Result<Vec<Article>> {
|
|
||||||
let mut articles: Vec<Article> = vec![];
|
|
||||||
|
|
||||||
for entry in WalkDir::new(&config.get_articles_path())
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|e| e.ok())
|
|
||||||
.filter(|e| e.path().extension().map(|ex| ex == "org").unwrap_or(false))
|
|
||||||
{
|
|
||||||
let file_path = entry.path().canonicalize()?;
|
|
||||||
|
|
||||||
let article_data = Vector::from_value_unchecked(
|
|
||||||
env.call(
|
|
||||||
"org-static-file-info",
|
|
||||||
[file_path
|
|
||||||
.to_str()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_string()
|
|
||||||
.into_lisp(env)?],
|
|
||||||
)?,
|
|
||||||
3,
|
|
||||||
);
|
|
||||||
|
|
||||||
let title = article_data.get::<String>(0)?;
|
|
||||||
let author = article_data.get::<String>(1)?;
|
|
||||||
let date = article_data.get::<String>(2)?;
|
|
||||||
let slug = {
|
|
||||||
let s = article_data.get::<String>(3)?;
|
|
||||||
if s.is_empty() {
|
|
||||||
file_path
|
|
||||||
.file_name()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_str()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_string()
|
|
||||||
} else {
|
|
||||||
s
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let tags = article_data
|
|
||||||
.get::<String>(4)?
|
|
||||||
.split(':')
|
|
||||||
.filter(|i| !i.is_empty())
|
|
||||||
.map(|i| i.to_string())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
articles.push(Article {
|
|
||||||
title,
|
|
||||||
slug,
|
|
||||||
date,
|
|
||||||
author,
|
|
||||||
file_path,
|
|
||||||
tags,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(articles)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn publish_articles(env: &Env, config: &config::Config, articles: &[Article]) -> Result<()> {
|
|
||||||
let eng = TemplateEngine::new(config.get_partials_path())?;
|
|
||||||
for article in articles {
|
|
||||||
std::fs::create_dir_all(article.get_build_path(config))?;
|
|
||||||
|
|
||||||
let content = env
|
|
||||||
.call(
|
|
||||||
"org-static-file-contents",
|
|
||||||
[article
|
|
||||||
.file_path
|
|
||||||
.to_str()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_string()
|
|
||||||
.into_lisp(env)?],
|
|
||||||
)?
|
|
||||||
.into_rust::<String>()?;
|
|
||||||
|
|
||||||
let ctx = ArticleContext::new(article.clone(), content);
|
|
||||||
|
|
||||||
eng.render_file(
|
|
||||||
config.get_theme_path().join("articles.html"),
|
|
||||||
article.get_build_path(config).join("index.html"),
|
|
||||||
&ctx,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[defun]
|
#[defun]
|
||||||
pub fn publish(env: &Env, starting_path: String) -> Result<()> {
|
fn say_hello(env: &Env, _name: String) -> Result<Value<'_>> {
|
||||||
let cfg = config::Config::find(PathBuf::from(starting_path))?;
|
let fname = env
|
||||||
println!("{:?}", cfg);
|
.call("buffer-file-name", [])
|
||||||
let articles = find_articles(env, &cfg)?;
|
.unwrap()
|
||||||
env.message(format!("{} articles found", articles.len()))?;
|
.into_rust::<String>()
|
||||||
publish_articles(env, &cfg, &articles)?;
|
.unwrap();
|
||||||
Ok(())
|
env.message(fname)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default, Debug)]
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
pub struct Site {
|
pub struct Site {
|
||||||
title: String,
|
title: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests {
|
pub mod tests {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -16,4 +15,7 @@ pub mod tests {
|
||||||
title: "Test Site".to_string(),
|
title: "Test Site".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_name() {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,61 +1,22 @@
|
||||||
use std::{collections::HashMap, path::PathBuf};
|
|
||||||
|
|
||||||
use handlebars::Handlebars;
|
use handlebars::Handlebars;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::error::Result;
|
use crate::{error::Result, site::Site};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
pub struct Template {
|
||||||
pub struct TemplateEngine {
|
pub template_string: String,
|
||||||
partials: HashMap<String, String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TemplateEngine {
|
impl Template {
|
||||||
pub fn new<T: Into<PathBuf>>(partial_path: T) -> Result<Self> {
|
pub fn new(template_string: String) -> Self {
|
||||||
let pp: PathBuf = partial_path.into();
|
Self { template_string }
|
||||||
|
|
||||||
let partials = if pp.exists() {
|
|
||||||
pp.read_dir()?
|
|
||||||
.filter_map(|i| i.ok())
|
|
||||||
.fold(HashMap::new(), |mut acc, i| {
|
|
||||||
if i.path().is_file() {
|
|
||||||
if let Some(file_name) = i.path().with_extension("").file_name() {
|
|
||||||
acc.insert(
|
|
||||||
file_name.to_str().unwrap_or_default().to_string(),
|
|
||||||
std::fs::read_to_string(i.path()).unwrap_or_default(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
acc
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
HashMap::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self { partials })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_string<D: Serialize>(&self, template_string: &str, data: D) -> Result<String> {
|
pub fn generate<T: Serialize>(&self, data: &T) -> Result<String> {
|
||||||
let mut reg = Handlebars::new();
|
let reg = Handlebars::new();
|
||||||
for (k, v) in self.partials.iter() {
|
|
||||||
reg.register_partial(k, v)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let res = reg.render_template(template_string, &data)?;
|
let output = reg.render_template(&self.template_string, data)?;
|
||||||
|
Ok(output)
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render_file<D: Serialize>(
|
|
||||||
&self,
|
|
||||||
template_file: PathBuf,
|
|
||||||
output_path: PathBuf,
|
|
||||||
data: D,
|
|
||||||
) -> Result<()> {
|
|
||||||
let template_content = std::fs::read_to_string(template_file)?;
|
|
||||||
let res = self.render_string(&template_content, data)?;
|
|
||||||
std::fs::write(output_path, res)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,11 +45,12 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_template() {
|
fn test_template() {
|
||||||
let eng = TemplateEngine::default();
|
let tmpl = Template::new(
|
||||||
|
"Hello, my name is {{ name }}, I am {{ age }} years old and i like {{#each tags}}{{this}} {{/each}}"
|
||||||
let template_string = "Hello, my name is {{ name }}, I am {{ age }} years old and i like {{#each tags}}{{this}} {{/each}}";
|
.to_string(),
|
||||||
let res = eng
|
);
|
||||||
.render_string(template_string, &TestData::default())
|
let res = tmpl
|
||||||
|
.generate(&TestData::default())
|
||||||
.expect("Error parsing template");
|
.expect("Error parsing template");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res,
|
res,
|
||||||
|
@ -103,13 +65,12 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let template_string = "Hello, my name is {{{ name }}}, I am {{ age }} years old and i like {{#each tags}}{{this}} {{/each}}";
|
let tmpl = Template::new(
|
||||||
|
"Hello, my name is {{{ name }}}, I am {{ age }} years old and i like {{#each tags}}{{this}} {{/each}}"
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
let eng = TemplateEngine::default();
|
let res = tmpl.generate(&data).expect("Error parsing template");
|
||||||
|
|
||||||
let res = eng
|
|
||||||
.render_string(template_string, &data)
|
|
||||||
.expect("Error parsing template");
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res,
|
res,
|
||||||
"Hello, my name is <b>Alice</b> Allison, I am 21 years old and i like cats dogs rust "
|
"Hello, my name is <b>Alice</b> Allison, I am 21 years old and i like cats dogs rust "
|
||||||
|
|
257
test.html
257
test.html
|
@ -1,257 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
|
||||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
|
|
||||||
<head>
|
|
||||||
<!-- 2022-12-21 Wed 01:30 -->
|
|
||||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<title>‎</title>
|
|
||||||
<meta name="author" content="Joe Bellus" />
|
|
||||||
<meta name="generator" content="Org Mode" />
|
|
||||||
<style type="text/css">
|
|
||||||
<!--/*--><![CDATA[/*><!--*/
|
|
||||||
html, body {
|
|
||||||
background: #181818;
|
|
||||||
font-family: sans-serif;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
width: 100vw;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
a, a:active, a:visited {
|
|
||||||
text-decoration: none;
|
|
||||||
color: #de0192;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1.title {
|
|
||||||
display: fixed;
|
|
||||||
margin-top: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 60px;
|
|
||||||
line-height: 60px;
|
|
||||||
font-weight: 300;
|
|
||||||
color: #888;
|
|
||||||
padding: 0 15px;
|
|
||||||
background: #131313;
|
|
||||||
border-bottom: 1px solid #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-weight: 300;
|
|
||||||
color: #de0192;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2:after {
|
|
||||||
content: ' ';
|
|
||||||
height: 2px;
|
|
||||||
display: block;
|
|
||||||
background: linear-gradient(90deg, #8604e2 60%, #de0192 100%);
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
text-transform: initial
|
|
||||||
}
|
|
||||||
|
|
||||||
.outline-text-4 p {
|
|
||||||
margin-top: 0;
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-bottom: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.figure,
|
|
||||||
.outline-2 {
|
|
||||||
max-width: 800px;
|
|
||||||
padding: 0px 25px;
|
|
||||||
margin-left: 350px;
|
|
||||||
color: #fcfcfc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.figure img {
|
|
||||||
box-shadow: rgba(0, 0, 0, 0.8) 0px 20px 30px -10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.outline-2, .outline-3 {
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-ul, .org-ul li {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
list-style: square;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-ul {
|
|
||||||
padding-left: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.figure img {
|
|
||||||
max-width:100%;
|
|
||||||
max-height:100%;
|
|
||||||
margin: auto auto;
|
|
||||||
border-radius: 5px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
#table-of-contents {
|
|
||||||
position: absolute;
|
|
||||||
width: 300px;
|
|
||||||
top: 61px;
|
|
||||||
left: 0;
|
|
||||||
color: #f0f0f0;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 0 0 5px 0;
|
|
||||||
background: #151515;
|
|
||||||
border-right: 1px solid #000;
|
|
||||||
border-bottom: 1px solid #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
#table-of-contents h2 {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#table-of-contents h2:after {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#text-table-of-contents > ul {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#table-of-contents li {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 3px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#text-table-of-contents > ul {
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#text-table-of-contents > ul ul {
|
|
||||||
font-weight: 500;
|
|
||||||
list-style: square;
|
|
||||||
margin-left: 5px;
|
|
||||||
padding: 3px 0;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#table-of-contents ul ul {
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#table-of-contents a,
|
|
||||||
#table-of-contents a:hover,
|
|
||||||
#table-of-contents a:active,
|
|
||||||
#table-of-contents a:visited {
|
|
||||||
text-decoration: none;
|
|
||||||
color: #ffffff;
|
|
||||||
transition: color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
#table-of-contents a:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
color: #de0192;
|
|
||||||
transition: color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-src-container {
|
|
||||||
margin-top: 5px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
width: 100%;
|
|
||||||
background: #000;
|
|
||||||
border-radius: 15px;
|
|
||||||
padding: 10px;
|
|
||||||
color: #fff;
|
|
||||||
font-weight: 600;
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
#postamble {
|
|
||||||
margin-top: 25px;
|
|
||||||
font-weight: 300;
|
|
||||||
color: #999;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 700;
|
|
||||||
padding: 25px 15px;
|
|
||||||
background: #131313;
|
|
||||||
border-top: 1px solid #000;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
color: #f5f5f5;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1000px) {
|
|
||||||
#content > p,
|
|
||||||
h1,
|
|
||||||
.figure,
|
|
||||||
.outline-2 {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#table-of-contents {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.org-src-container {
|
|
||||||
width: 100%;
|
|
||||||
overflow-x: scroll;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
#content .title + .org-ul {
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
.title + .org-ul > li b {
|
|
||||||
flex-direction: column-reverse;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.title + .org-ul > li b a {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*]]>*/-->
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="content" class="content">
|
|
||||||
<div class="org-src-container">
|
|
||||||
<pre class="src src-emacs-lisp"><span style="color: #c792ea;">(</span><span style="color: #89DDFF;">progn</span>
|
|
||||||
<span style="color: #f78c6c;">(</span>add-to-list 'load-path <span style="color: #c3e88d;">"/media/joe/extradrive1/Projects/fivesigma/org-static/target/debug"</span><span style="color: #f78c6c;">)</span>
|
|
||||||
<span style="color: #f78c6c;">(</span><span style="color: #89DDFF;">require</span> '<span style="color: #f78c6c;">org-static</span><span style="color: #f78c6c;">)</span>
|
|
||||||
<span style="color: #f78c6c;">(</span>org-static-say-hello <span style="color: #c3e88d;">"joe"</span><span style="color: #f78c6c;">)</span><span style="color: #c792ea;">)</span>
|
|
||||||
|
|
||||||
<span style="color: #c792ea;">(</span>buffer-file-name<span style="color: #c792ea;">)</span>
|
|
||||||
|
|
||||||
<span style="color: #c792ea;">(</span>org-export-to-file 'html <span style="color: #c3e88d;">"test.html"</span><span style="color: #c792ea;">)</span>
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="postamble" class="status">
|
|
||||||
<p class="author">Author: Joe Bellus</p>
|
|
||||||
<p class="date">Created: 2022-12-21 Wed 01:30</p>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Loading…
Reference in New Issue