Sync commit
This commit is contained in:
parent
b70bb8e542
commit
860fa79187
|
@ -1 +1,2 @@
|
|||
target/
|
||||
build/
|
|
@ -180,9 +180,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.138"
|
||||
version = "0.2.139"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
|
||||
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
|
@ -208,13 +208,15 @@ dependencies = [
|
|||
"lazy_static",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"toml",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.5.1"
|
||||
version = "2.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc8bed3549e0f9b0a2a78bf7c0018237a2cdf085eecbbc048e52612438e4e9d0"
|
||||
checksum = "0f6e86fb9e7026527a0d46bc308b841d73170ef8f443e1807f6ef88526a816d4"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
"ucd-trie",
|
||||
|
@ -222,9 +224,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.5.1"
|
||||
version = "2.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdc078600d06ff90d4ed238f0119d84ab5d43dbaad278b0e33a8820293b32344"
|
||||
checksum = "96504449aa860c8dcde14f9fba5c58dc6658688ca1fe363589d6327b8662c603"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
|
@ -232,9 +234,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.5.1"
|
||||
version = "2.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28a1af60b1c4148bb269006a750cff8e2ea36aff34d2d96cf7be0b14d1bed23c"
|
||||
checksum = "798e0220d1111ae63d66cb66a5dcb3fc2d986d520b98e49e1852bfdb11d7c5e7"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
|
@ -245,9 +247,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.5.1"
|
||||
version = "2.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fec8605d59fc2ae0c6c1aefc0c7c7a9769732017c0ce07f7a9cfffa7b4404f20"
|
||||
checksum = "984298b75898e30a843e278a9f2452c31e349a073a0ce6fd950a12a74464e065"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"pest",
|
||||
|
@ -287,6 +289,15 @@ version = "1.0.12"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "semver"
|
||||
version = "0.9.0"
|
||||
|
@ -381,6 +392,15 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.16.0"
|
||||
|
@ -404,3 +424,45 @@ name = "version_check"
|
|||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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,22 +3,18 @@ name = "org-static"
|
|||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
lazy_static = "1.4"
|
||||
emacs = "0.18.0"
|
||||
|
||||
serde = { version = "1.0.151", features = ["derive"] }
|
||||
handlebars = "4.3.6"
|
||||
serde_json = "1.0.91"
|
||||
|
||||
[dev-dependencies]
|
||||
#emacs-rs-module = { version = "0.13.0" }
|
||||
walkdir = "2.3.2"
|
||||
toml = "0.5.10"
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
use org_static::publish;
|
||||
|
||||
fn main() {
|
||||
match publish("examples/basic".into()) {
|
||||
Ok(()) => println!("Complete"),
|
||||
Err(e) => println!("Error: {}", e),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
#+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
|
|
@ -0,0 +1,2 @@
|
|||
[site]
|
||||
title = "Basic Example Site"
|
|
@ -0,0 +1,5 @@
|
|||
<div class="menu">Some Menu</div>
|
||||
<div>title: {{ article.title }}</div>
|
||||
<Article>
|
||||
{{{content}}}
|
||||
</Article>
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
|
||||
#+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]]
|
118
src/article.rs
118
src/article.rs
|
@ -1,16 +1,26 @@
|
|||
use crate::{error::Result, site::Site, template::Template};
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::{collections::HashSet, path::PathBuf};
|
||||
|
||||
#[derive(Serialize, Default)]
|
||||
use crate::config::Config;
|
||||
|
||||
#[derive(Serialize, Default, Clone)]
|
||||
pub struct Article {
|
||||
title: String,
|
||||
filename: String,
|
||||
date: String,
|
||||
author: String,
|
||||
properties: HashMap<String, String>,
|
||||
tags: HashSet<String>,
|
||||
pub title: String,
|
||||
pub date: String,
|
||||
pub author: String,
|
||||
pub slug: String,
|
||||
pub file_path: PathBuf,
|
||||
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)]
|
||||
|
@ -20,91 +30,7 @@ pub struct ArticleContext {
|
|||
}
|
||||
|
||||
impl ArticleContext {
|
||||
pub fn load(article: Article) -> Result<Self> {
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn generate_article_index(
|
||||
site: &Site,
|
||||
articles: Vec<Article>,
|
||||
template: &Template,
|
||||
) -> Result<String> {
|
||||
let ctx = json!({
|
||||
"site": site,
|
||||
"data": articles,
|
||||
});
|
||||
|
||||
let res = template.generate(&ctx)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{site::tests::fixture_site, template::Template};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn fixture_article() -> Article {
|
||||
Article {
|
||||
title: "Test Article #1".to_string(),
|
||||
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_article() {
|
||||
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 = 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");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_article_index() {
|
||||
let articles = vec![fixture_article(), fixture_article()];
|
||||
let site = fixture_site();
|
||||
let template = Template::new("list: {{#each data}}{{title}} {{/each}}".to_string());
|
||||
let res =
|
||||
generate_article_index(&site, articles, &template).expect("couldnt generate template");
|
||||
|
||||
assert_eq!(res, "list: Test Article #1 Test Article #1 ");
|
||||
pub fn new(article: Article, content: String) -> Self {
|
||||
Self { article, content }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
(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,8 +11,6 @@ impl std::fmt::Display for Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl Error {
|
||||
pub fn new(message: &str) -> Self {
|
||||
Error {
|
||||
|
@ -21,18 +19,16 @@ impl Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<handlebars::TemplateError> for Error {
|
||||
fn from(source: handlebars::TemplateError) -> Self {
|
||||
impl<T: std::error::Error> From<T> for Error {
|
||||
fn from(source: T) -> Self {
|
||||
Self {
|
||||
message: source.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<handlebars::RenderError> for Error {
|
||||
fn from(source: handlebars::RenderError) -> Self {
|
||||
Self {
|
||||
message: source.to_string(),
|
||||
}
|
||||
impl From<Error> for emacs::Error {
|
||||
fn from(source: Error) -> Self {
|
||||
emacs::Error::msg(source)
|
||||
}
|
||||
}
|
||||
|
|
112
src/lib.rs
112
src/lib.rs
|
@ -1,25 +1,117 @@
|
|||
#![allow(non_snake_case)]
|
||||
|
||||
mod article;
|
||||
mod config;
|
||||
mod error;
|
||||
mod site;
|
||||
mod template;
|
||||
|
||||
use emacs::{defun, Env, Result, Value};
|
||||
use article::{Article, ArticleContext};
|
||||
use std::path::PathBuf;
|
||||
use template::TemplateEngine;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use emacs::{defun, Env, IntoLisp, Result, Vector};
|
||||
|
||||
emacs::plugin_is_GPL_compatible!();
|
||||
|
||||
#[emacs::module(name = "org-static")]
|
||||
fn init(_: &Env) -> Result<()> {
|
||||
#[emacs::module(name = "liborg_static", defun_prefix = "org-static")]
|
||||
fn init(e: &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(())
|
||||
}
|
||||
|
||||
#[defun]
|
||||
fn say_hello(env: &Env, _name: String) -> Result<Value<'_>> {
|
||||
let fname = env
|
||||
.call("buffer-file-name", [])
|
||||
.unwrap()
|
||||
.into_rust::<String>()
|
||||
.unwrap();
|
||||
env.message(fname)
|
||||
pub fn publish(env: &Env, starting_path: String) -> Result<()> {
|
||||
let cfg = config::Config::find(PathBuf::from(starting_path))?;
|
||||
println!("{:?}", cfg);
|
||||
let articles = find_articles(env, &cfg)?;
|
||||
env.message(format!("{} articles found", articles.len()))?;
|
||||
publish_articles(env, &cfg, &articles)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
#[derive(Serialize, Deserialize, Default, Debug)]
|
||||
pub struct Site {
|
||||
title: String,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
@ -15,7 +16,4 @@ pub mod tests {
|
|||
title: "Test Site".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_name() {}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,61 @@
|
|||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use handlebars::Handlebars;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{error::Result, site::Site};
|
||||
use crate::error::Result;
|
||||
|
||||
pub struct Template {
|
||||
pub template_string: String,
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TemplateEngine {
|
||||
partials: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Template {
|
||||
pub fn new(template_string: String) -> Self {
|
||||
Self { template_string }
|
||||
impl TemplateEngine {
|
||||
pub fn new<T: Into<PathBuf>>(partial_path: T) -> Result<Self> {
|
||||
let pp: PathBuf = partial_path.into();
|
||||
|
||||
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 generate<T: Serialize>(&self, data: &T) -> Result<String> {
|
||||
let reg = Handlebars::new();
|
||||
pub fn render_string<D: Serialize>(&self, template_string: &str, data: D) -> Result<String> {
|
||||
let mut reg = Handlebars::new();
|
||||
for (k, v) in self.partials.iter() {
|
||||
reg.register_partial(k, v)?;
|
||||
}
|
||||
|
||||
let output = reg.render_template(&self.template_string, data)?;
|
||||
Ok(output)
|
||||
let res = reg.render_template(template_string, &data)?;
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,12 +84,11 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_template() {
|
||||
let tmpl = Template::new(
|
||||
"Hello, my name is {{ name }}, I am {{ age }} years old and i like {{#each tags}}{{this}} {{/each}}"
|
||||
.to_string(),
|
||||
);
|
||||
let res = tmpl
|
||||
.generate(&TestData::default())
|
||||
let eng = TemplateEngine::default();
|
||||
|
||||
let template_string = "Hello, my name is {{ name }}, I am {{ age }} years old and i like {{#each tags}}{{this}} {{/each}}";
|
||||
let res = eng
|
||||
.render_string(template_string, &TestData::default())
|
||||
.expect("Error parsing template");
|
||||
assert_eq!(
|
||||
res,
|
||||
|
@ -65,12 +103,13 @@ mod tests {
|
|||
..Default::default()
|
||||
};
|
||||
|
||||
let tmpl = Template::new(
|
||||
"Hello, my name is {{{ name }}}, I am {{ age }} years old and i like {{#each tags}}{{this}} {{/each}}"
|
||||
.to_string(),
|
||||
);
|
||||
let template_string = "Hello, my name is {{{ name }}}, I am {{ age }} years old and i like {{#each tags}}{{this}} {{/each}}";
|
||||
|
||||
let res = tmpl.generate(&data).expect("Error parsing template");
|
||||
let eng = TemplateEngine::default();
|
||||
|
||||
let res = eng
|
||||
.render_string(template_string, &data)
|
||||
.expect("Error parsing template");
|
||||
assert_eq!(
|
||||
res,
|
||||
"Hello, my name is <b>Alice</b> Allison, I am 21 years old and i like cats dogs rust "
|
||||
|
|
|
@ -0,0 +1,257 @@
|
|||
<?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