Move to binary crate only, move to 'anyhow' for error handling.
This commit is contained in:
parent
20accb8d66
commit
38e1337e08
|
@ -18,6 +18,12 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b"
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
version = "0.1.4"
|
||||
|
@ -32,6 +38,7 @@ dependencies = [
|
|||
name = "arse"
|
||||
version = "0.4.0-alpha.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
"clap",
|
||||
"data-encoding",
|
||||
|
@ -43,7 +50,6 @@ dependencies = [
|
|||
"routerify",
|
||||
"serde",
|
||||
"simplelog",
|
||||
"snafu",
|
||||
"tempfile",
|
||||
"tera",
|
||||
"tokio",
|
||||
|
@ -233,12 +239,6 @@ dependencies = [
|
|||
"generic-array 0.14.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "fake-simd"
|
||||
version = "0.1.2"
|
||||
|
@ -964,27 +964,6 @@ version = "1.6.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
|
||||
|
||||
[[package]]
|
||||
name = "snafu"
|
||||
version = "0.6.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7"
|
||||
dependencies = [
|
||||
"doc-comment",
|
||||
"snafu-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snafu-derive"
|
||||
version = "0.6.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.0"
|
||||
|
|
|
@ -14,6 +14,7 @@ categories = ["command-line-utilities", "web-programming::http-server"]
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.40"
|
||||
argon2 = "0.1"
|
||||
clap = "2.33.3"
|
||||
data-encoding = "2.3.2"
|
||||
|
@ -25,7 +26,6 @@ rand = "0.8"
|
|||
routerify = "2.0.0"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
simplelog = "0.10.0"
|
||||
snafu = { version = "0.6.10", default-features = false, features = ["std"] }
|
||||
tera = "1.7.0"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
toml = "0.5"
|
||||
|
|
|
@ -8,18 +8,12 @@ and flexible base for serving sites using:
|
|||
* [routerify](https://crates.io/crates/routerify) to serve the site
|
||||
* [simplecss](https://simplecss.org) for default styling
|
||||
|
||||
## Binary
|
||||
## Usage
|
||||
|
||||
* Run an existing site given the path to its config TOML: `arse run config.toml`
|
||||
* Create and run a new site from user input: `arse new`
|
||||
* Logging verbosity can be increased with `-v` or `-vv`, the default level is `INFO`.
|
||||
|
||||
## Library
|
||||
|
||||
`arse` can be used as a library to extend functionality as the user sees fit.
|
||||
|
||||
Documentation can be found [here](https://docs.rs/arse/).
|
||||
|
||||
## Path to 1.0
|
||||
|
||||
- [x] Dynamic route handling
|
||||
|
|
27
src/auth.rs
27
src/auth.rs
|
@ -8,20 +8,23 @@ http://opensource.org/licenses/MIT>, at your option. This file may not be
|
|||
copied, modified, or distributed except according to those terms.
|
||||
*/
|
||||
|
||||
use std::usize;
|
||||
|
||||
use log::{debug, error};
|
||||
|
||||
use super::{Result, Error};
|
||||
use super::{anyhow, Result};
|
||||
|
||||
/**
|
||||
TODO Document
|
||||
*/
|
||||
pub fn generate_secret(len: usize) -> Result<String> {
|
||||
pub(crate) fn generate_secret(len: usize) -> Result<String> {
|
||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||
let min = 32;
|
||||
const MIN: usize = 32;
|
||||
|
||||
if len < min {
|
||||
error!("Attempting to use password < 32ch.");
|
||||
Err(Error::WeakSecret { min })
|
||||
if len < MIN {
|
||||
let msg = format!("Attempting to create secret with < {}ch", &MIN);
|
||||
error!("{}", &msg);
|
||||
Err(anyhow!("{}", msg))
|
||||
} else {
|
||||
let pass: String = thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
|
@ -36,7 +39,7 @@ pub fn generate_secret(len: usize) -> Result<String> {
|
|||
/**
|
||||
TODO Document
|
||||
*/
|
||||
pub fn generate_argon2_phc(secret: &str) -> Result<String> {
|
||||
pub(crate) fn generate_argon2_phc(secret: &str) -> Result<String> {
|
||||
use argon2::{
|
||||
password_hash::{PasswordHasher, SaltString},
|
||||
Argon2,
|
||||
|
@ -50,19 +53,21 @@ pub fn generate_argon2_phc(secret: &str) -> Result<String> {
|
|||
|
||||
match argon2.hash_password_simple(secret, salt.as_ref()) {
|
||||
Ok(phc) => {
|
||||
debug!("Created Argon2 PHC string");
|
||||
let msg = "Created Argon2 PHC string";
|
||||
debug!("{}", msg);
|
||||
argon2_phc = Ok(phc.to_string());
|
||||
}
|
||||
Err(_) => {
|
||||
error!("Failed to create Argon2 PHC");
|
||||
argon2_phc = Err(Error::HasherError);
|
||||
let msg = "Failed to create Argon2 PHC";
|
||||
error!("{}", &msg);
|
||||
argon2_phc = Err(anyhow!("{}", msg));
|
||||
}
|
||||
}
|
||||
|
||||
argon2_phc
|
||||
}
|
||||
|
||||
pub use data_encoding::BASE32_NOPAD;
|
||||
pub(crate) use data_encoding::BASE32_NOPAD;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
@ -15,8 +15,10 @@ use std::path::{Path, PathBuf};
|
|||
use glob::glob;
|
||||
use log::{debug, trace};
|
||||
|
||||
use super::{Context, Result};
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
pub fn str_to_ro_file<P: AsRef<Path>>(content: &str, dest: P) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub(crate) fn str_to_ro_file<P: AsRef<Path>>(content: &str, dest: P) -> Result<()> {
|
||||
debug!("Writing protected file: {}", &dest.as_ref().display());
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
let mut options = OpenOptions::new();
|
||||
|
@ -25,25 +27,32 @@ pub fn str_to_ro_file<P: AsRef<Path>>(content: &str, dest: P) -> Result<(), Box<
|
|||
options.mode(0o600);
|
||||
|
||||
trace!("Opening '{}' to write", &dest.as_ref().display());
|
||||
let mut ro_file = options.open(dest)?;
|
||||
ro_file.write_all(content.as_bytes())?;
|
||||
let mut ro_file = options.open(&dest)
|
||||
.with_context(|| format!("failed to open '{}' for writing", &dest.as_ref().display()))?;
|
||||
ro_file.write_all(content.as_bytes())
|
||||
.with_context(|| format!("failure writing '{}'", &dest.as_ref().display()))?;
|
||||
if !content.ends_with('\n') {
|
||||
ro_file.write_all(b"\n")?;
|
||||
ro_file.write_all(b"\n")
|
||||
.with_context(|| format!("failure writing '{}'", &dest.as_ref().display()))?;
|
||||
}
|
||||
trace!("Content written to destination");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
pub fn str_to_ro_file<P: AsRef<Path>>(content: &str, dest: P) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub fn str_to_ro_file<P: AsRef<Path>>(content: &str, dest: P) -> Result<()> {
|
||||
debug!("Writing protected file: {}", &dest.as_ref().display());
|
||||
trace!("Opening '{}' to write", &dest.as_ref().display());
|
||||
let mut ro_file = File::create(dest)?;
|
||||
ro_file.write_all(content.as_bytes())?;
|
||||
let metadata = secret_file.metadata()?;
|
||||
let mut ro_file = File::create(&dest)
|
||||
.with_context(|| format!("failed to open '{}' for writing", &dest.as_ref().display()))?;
|
||||
ro_file.write_all(content.as_bytes()).
|
||||
with_context(|| format!("failure writing '{}'", &dest.as_ref().display()))?;
|
||||
let metadata = secret_file.metadata()
|
||||
.with_context(|| format!("failure retrieving metadata on '{}'", &dest.as_ref().display()))?;
|
||||
let mut perms = metadata.permissions();
|
||||
if !content.ends_with('\n') {
|
||||
ro_file.write_all(b"\n")?;
|
||||
ro_file.write_all(b"\n")
|
||||
.with_context("failure writing '{}'", &dest.as_ref().display())?;
|
||||
}
|
||||
trace!("Content written to destination");
|
||||
trace!("Setting read-only on destination file");
|
||||
|
@ -51,12 +60,13 @@ pub fn str_to_ro_file<P: AsRef<Path>>(content: &str, dest: P) -> Result<(), Box<
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn path_matches(pat: &str) -> Result<Vec<PathBuf>, Box<dyn std::error::Error>> {
|
||||
pub fn path_matches(pat: &str) -> Result<Vec<PathBuf>> {
|
||||
debug!("Building topic content vector from {}", &pat);
|
||||
let mut path_vec: Vec<PathBuf> = Vec::new();
|
||||
|
||||
trace!("Globbing {}", &pat);
|
||||
let entries = glob(pat)?;
|
||||
let entries = glob(pat)
|
||||
.context("failure globbing paths")?;
|
||||
|
||||
for entry in entries.filter_map(Result::ok) {
|
||||
trace!("Adding '{}' to topic content vector", &entry.display());
|
||||
|
|
|
@ -19,6 +19,7 @@ use serde::{Serialize, Deserialize};
|
|||
|
||||
use super::auth;
|
||||
use super::common;
|
||||
use super::{anyhow, Context, Result};
|
||||
|
||||
fn args() -> App<'static, 'static> {
|
||||
App::new("A Rust Site Engine")
|
||||
|
@ -44,8 +45,8 @@ fn args() -> App<'static, 'static> {
|
|||
|
||||
/// TODO Document this public function
|
||||
/// And Include an Example of its Use
|
||||
pub fn load() -> Result<AppConfig, Box<dyn std::error::Error>> {
|
||||
let config: Result<AppConfig, Box<dyn std::error::Error>>;
|
||||
pub(crate) fn load() -> Result<AppConfig> {
|
||||
let config: Result<AppConfig>;
|
||||
let matches = args().get_matches();
|
||||
|
||||
// Create a Config with ISO timestamps
|
||||
|
@ -55,9 +56,9 @@ pub fn load() -> Result<AppConfig, Box<dyn std::error::Error>> {
|
|||
|
||||
// After this block locking is configured at the specified level
|
||||
match matches.occurrences_of("verbosity") {
|
||||
0 => SimpleLogger::init(log::LevelFilter::Info, log_config)?,
|
||||
1 => SimpleLogger::init(log::LevelFilter::Debug, log_config)?,
|
||||
_ => SimpleLogger::init(log::LevelFilter::Trace, log_config)?,
|
||||
0 => SimpleLogger::init(log::LevelFilter::Info, log_config).context("failed to initialize logger at level - INFO")?,
|
||||
1 => SimpleLogger::init(log::LevelFilter::Debug, log_config).context("failed to initialize logger at level - DEBUG")?,
|
||||
_ => SimpleLogger::init(log::LevelFilter::Trace, log_config).context("failed to initialize logger at level - TRACE")?,
|
||||
}
|
||||
|
||||
info!("Logging started");
|
||||
|
@ -70,18 +71,18 @@ pub fn load() -> Result<AppConfig, Box<dyn std::error::Error>> {
|
|||
trace!("Application called with `new` subcommand - creating config from user input");
|
||||
let reader = std::io::stdin();
|
||||
let mut reader = reader.lock();
|
||||
let current_path = std::env::current_dir()?;
|
||||
let current_path = std::env::current_dir().context("failed to get current working directory")?;
|
||||
config = AppConfig::generate(current_path, &mut reader);
|
||||
} else {
|
||||
let msg = "Unable to load configuration".to_owned();
|
||||
error!("{}", &msg);
|
||||
config = Err(From::from(msg));
|
||||
config = Err(anyhow!("{}", msg));
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
fn runner_config(m: ArgMatches) -> Result<AppConfig, Box<dyn std::error::Error>> {
|
||||
fn runner_config(m: ArgMatches) -> Result<AppConfig> {
|
||||
if let Some(run) = m.subcommand_matches("run") {
|
||||
let value = run.value_of("config").unwrap();
|
||||
let config = AppConfig::from_path(value)?;
|
||||
|
@ -89,14 +90,15 @@ fn runner_config(m: ArgMatches) -> Result<AppConfig, Box<dyn std::error::Error>>
|
|||
} else {
|
||||
let msg = "Failed to read arguments for 'run' subcommand".to_owned();
|
||||
error!("{}", &msg);
|
||||
Err(From::from(msg))
|
||||
Err(anyhow!("{}", msg))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_input<R: BufRead>(prompt: &str, reader: &mut R) -> Result<String, Box<dyn std::error::Error>> {
|
||||
fn get_input<R: BufRead>(prompt: &str, reader: &mut R) -> Result<String> {
|
||||
let mut buf = String::new();
|
||||
println!("{}", prompt);
|
||||
reader.read_line(&mut buf)?;
|
||||
reader.read_line(&mut buf)
|
||||
.context("failed reading input from user")?;
|
||||
let buf = String::from(buf
|
||||
.trim_start_matches(char::is_whitespace)
|
||||
.trim_end_matches(char::is_whitespace));
|
||||
|
@ -117,14 +119,14 @@ fn csv_to_vec(csv: &str) -> Vec<String> {
|
|||
|
||||
/// TODO Document
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct Site {
|
||||
pub(crate) struct Site {
|
||||
pub name: String,
|
||||
pub author: String,
|
||||
pub topics: Vec<String>,
|
||||
}
|
||||
|
||||
impl Site {
|
||||
pub fn new_from_input<R: BufRead>(reader: &mut R) -> Result<Site, Box<dyn std::error::Error>> {
|
||||
pub(crate) fn new_from_input<R: BufRead>(reader: &mut R) -> Result<Site> {
|
||||
let name = get_input("Please enter a name for the site: ", reader)?;
|
||||
let author = get_input("Please enter the site author's name: ", reader)?;
|
||||
let topics = get_input("Please enter comma-separated site topics: ", reader)?;
|
||||
|
@ -138,14 +140,14 @@ impl Site {
|
|||
|
||||
/// TODO Document
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct Credentials {
|
||||
pub(crate) struct Credentials {
|
||||
pub user: String,
|
||||
pub password: String,
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
impl Credentials {
|
||||
pub fn new_from_input<P: AsRef<Path>, R: BufRead>(dir: P, reader: &mut R) -> Result<Credentials, Box<dyn std::error::Error>> {
|
||||
pub(crate) fn new_from_input<P: AsRef<Path>, R: BufRead>(dir: P, reader: &mut R) -> Result<Credentials> {
|
||||
let user = get_input("Please enter an username for the site admin: ", reader)?;
|
||||
const PASSWORD_LEN: usize = 32;
|
||||
let password = auth::generate_secret(PASSWORD_LEN)?;
|
||||
|
@ -170,13 +172,13 @@ impl Credentials {
|
|||
|
||||
/// TODO Document
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct DocPaths {
|
||||
pub(crate) struct DocPaths {
|
||||
pub templates: String,
|
||||
pub webroot: String,
|
||||
}
|
||||
|
||||
impl DocPaths {
|
||||
pub fn new<P: AsRef<Path>>(dir: P) -> Result<DocPaths, Box<dyn std::error::Error>> {
|
||||
pub(crate) fn new<P: AsRef<Path>>(dir: P) -> DocPaths {
|
||||
debug!("Creating site DocPaths");
|
||||
let dir = dir.as_ref().display();
|
||||
let templates = format!("{}/site/templates", dir);
|
||||
|
@ -184,32 +186,34 @@ impl DocPaths {
|
|||
let docpaths = DocPaths { templates, webroot };
|
||||
|
||||
trace!("Site DocPaths: {:?}", docpaths);
|
||||
Ok(docpaths)
|
||||
docpaths
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO Document
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct AppConfig {
|
||||
pub(crate) struct AppConfig {
|
||||
pub site: Site,
|
||||
pub creds: Credentials,
|
||||
pub docpaths: DocPaths,
|
||||
}
|
||||
|
||||
impl AppConfig {
|
||||
pub fn from_path<T: AsRef<Path>>(config: T) -> Result<AppConfig, Box<dyn std::error::Error>> {
|
||||
pub(crate) fn from_path<T: AsRef<Path>>(config: T) -> Result<AppConfig> {
|
||||
debug!("Loading site configuration from {}", &config.as_ref().display());
|
||||
let config = std::fs::read_to_string(config)?;
|
||||
let config_string = std::fs::read_to_string(&config)
|
||||
.with_context(|| format!("failed reading '{}' to string", &config.as_ref().display()))?;
|
||||
|
||||
trace!("Parsing configuration TOML");
|
||||
let app_config: AppConfig = toml::from_str(&config)?;
|
||||
let app_config: AppConfig = toml::from_str(&config_string)
|
||||
.context("failed to parse TOML")?;
|
||||
|
||||
Ok(app_config)
|
||||
}
|
||||
|
||||
pub fn generate<P: AsRef<Path>, R: BufRead>(dir: P, reader: &mut R) -> Result<AppConfig, Box<dyn std::error::Error>> {
|
||||
pub(crate) fn generate<P: AsRef<Path>, R: BufRead>(dir: P, reader: &mut R) -> Result<AppConfig> {
|
||||
debug!("Generating new site configuration");
|
||||
let docpaths = DocPaths::new(&dir)?;
|
||||
let docpaths = DocPaths::new(&dir);
|
||||
let site = Site::new_from_input(reader)?;
|
||||
let creds = Credentials::new_from_input(&dir, reader)?;
|
||||
|
||||
|
@ -219,13 +223,15 @@ impl AppConfig {
|
|||
docpaths,
|
||||
};
|
||||
|
||||
config.create_paths()?;
|
||||
config.write(&dir)?;
|
||||
config.create_paths()
|
||||
.context("failed while creating site paths")?;
|
||||
config.write(&dir)
|
||||
.context("failed to write site config to disk")?;
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn create_paths(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
fn create_paths(&self) -> Result<()> {
|
||||
debug!("Creating site filesystem tree");
|
||||
create_dir_all(&self.docpaths.templates)?;
|
||||
create_dir_all(format!("{}/static/ext", &self.docpaths.webroot))?;
|
||||
|
@ -241,9 +247,9 @@ impl AppConfig {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn write<P: AsRef<Path>>(&self, dir: P) -> Result<(), Box<dyn std::error::Error>> {
|
||||
fn write<P: AsRef<Path>>(&self, dir: P) -> Result<()> {
|
||||
debug!("Writing site configuration to disk");
|
||||
let config = toml::to_string_pretty(&self)?;
|
||||
let config = toml::to_string_pretty(&self).context("failure creating TOML")?;
|
||||
let conf_path = &dir.as_ref().join("config.toml");
|
||||
common::str_to_ro_file(&config, &conf_path)?;
|
||||
Ok(())
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
A Rust Site Engine
|
||||
Copyright 2020-2021 Anthony Martinez
|
||||
|
||||
Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
|
||||
http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
|
||||
http://opensource.org/licenses/MIT>, at your option. This file may not be
|
||||
copied, modified, or distributed except according to those terms.
|
||||
*/
|
||||
|
||||
use snafu::Snafu;
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum Error {
|
||||
#[snafu(display("Attempted to generate a secret with less than {} characters", min))]
|
||||
WeakSecret { min: usize },
|
||||
|
||||
#[snafu(display("Failed to create Argon2 PHC"))]
|
||||
HasherError,
|
||||
}
|
||||
|
19
src/lib.rs
19
src/lib.rs
|
@ -1,19 +0,0 @@
|
|||
/*
|
||||
A Rust Site Engine
|
||||
Copyright 2020-2021 Anthony Martinez
|
||||
|
||||
Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
|
||||
http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
|
||||
http://opensource.org/licenses/MIT>, at your option. This file may not be
|
||||
copied, modified, or distributed except according to those terms.
|
||||
*/
|
||||
|
||||
pub mod auth;
|
||||
pub mod config;
|
||||
pub mod common;
|
||||
pub mod render;
|
||||
pub mod routes;
|
||||
pub mod errors;
|
||||
|
||||
pub type Error = crate::errors::Error;
|
||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
|
@ -11,13 +11,19 @@ copied, modified, or distributed except according to those terms.
|
|||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use arse::*;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use log::{info, error};
|
||||
use hyper::Server;
|
||||
use routerify::RouterService;
|
||||
|
||||
mod auth;
|
||||
mod config;
|
||||
mod common;
|
||||
mod render;
|
||||
mod routes;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
async fn main() -> Result<()> {
|
||||
let config = config::load()?;
|
||||
let app = Arc::new(config);
|
||||
info!("Configuration loaded");
|
|
@ -13,15 +13,17 @@ use std::sync::Arc;
|
|||
|
||||
use super::config::AppConfig;
|
||||
use super::common;
|
||||
use super::{Context, Result};
|
||||
|
||||
use log::{debug, trace};
|
||||
use pulldown_cmark::{Parser, html};
|
||||
use tera::{Tera, Context};
|
||||
use tera::Tera;
|
||||
use tera::Context as TemplateContext;
|
||||
|
||||
mod default;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Engine {
|
||||
pub(crate) struct Engine {
|
||||
pub app: Arc<AppConfig>,
|
||||
pub topic_slug: String,
|
||||
pub template: String,
|
||||
|
@ -29,7 +31,7 @@ pub struct Engine {
|
|||
}
|
||||
|
||||
impl Engine {
|
||||
pub fn new(a: Arc<AppConfig>, ts: &str, tmpl: &str, inst: Tera) -> Engine {
|
||||
pub(crate) fn new(a: Arc<AppConfig>, ts: &str, tmpl: &str, inst: Tera) -> Engine {
|
||||
trace!("Loading rendering engine");
|
||||
Engine {
|
||||
app: a,
|
||||
|
@ -40,27 +42,29 @@ impl Engine {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn load_default_template() -> Result<Tera, Box<dyn std::error::Error>> {
|
||||
pub(crate) fn load_default_template() -> Result<Tera> {
|
||||
trace!("Loading default rendering template");
|
||||
let mut tera = Tera::default();
|
||||
tera.add_raw_template("default.tmpl", default::TEMPLATE)?;
|
||||
tera.add_raw_template("default.tmpl", default::TEMPLATE)
|
||||
.context("failure adding default template")?;
|
||||
Ok(tera)
|
||||
}
|
||||
|
||||
pub(crate) fn render_topic(engine: Engine) -> Result<String, Box<dyn std::error::Error>> {
|
||||
pub(crate) fn render_topic(engine: Engine) -> Result<String> {
|
||||
debug!("Rendering topic: '{}'", &engine.topic_slug);
|
||||
let site = &engine.app.site;
|
||||
let topic_data = load_topic(&engine)?;
|
||||
let mut context = Context::new();
|
||||
let mut context = TemplateContext::new();
|
||||
context.insert("site", site);
|
||||
context.insert("posts", &topic_data);
|
||||
let output = engine.instance.render(&engine.template, &context)?;
|
||||
let output = engine.instance.render(&engine.template, &context)
|
||||
.with_context(|| format!("failed rendering topic: {}", &engine.topic_slug))?;
|
||||
|
||||
trace!("Rendered content for topic: {}\n{}", &engine.topic_slug, output);
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn load_topic(engine: &Engine) -> Result<Vec<String>, Box<dyn std::error::Error>> {
|
||||
fn load_topic(engine: &Engine) -> Result<Vec<String>> {
|
||||
trace!("Loading topic content for '{}'", &engine.topic_slug);
|
||||
let topic_path = Path::new(&engine.app.docpaths.webroot).join(&engine.topic_slug).join("posts");
|
||||
let pat = format!("{}/*.md", topic_path.display());
|
||||
|
@ -68,12 +72,13 @@ fn load_topic(engine: &Engine) -> Result<Vec<String>, Box<dyn std::error::Error>
|
|||
read_to_html(paths)
|
||||
}
|
||||
|
||||
fn read_to_html(paths: Vec<PathBuf>) -> Result<Vec<String>, Box<dyn std::error::Error>> {
|
||||
fn read_to_html(paths: Vec<PathBuf>) -> Result<Vec<String>> {
|
||||
debug!("Rendering Markdown to HTML");
|
||||
let mut contents: Vec<String> = Vec::new();
|
||||
for path in paths {
|
||||
trace!("Rendering {} to HTML", &path.display());
|
||||
let buf = std::fs::read_to_string(path)?;
|
||||
let buf = std::fs::read_to_string(&path)
|
||||
.with_context(|| format!("failure reading '{}' to string", &path.display()))?;
|
||||
let parser = Parser::new(&buf);
|
||||
let mut html_output = String::new();
|
||||
html::push_html(&mut html_output, parser);
|
||||
|
|
|
@ -22,7 +22,7 @@ use super::config::AppConfig;
|
|||
use super::render;
|
||||
|
||||
|
||||
pub fn router(app: Arc<AppConfig>) -> Router<Body, Infallible> {
|
||||
pub(crate) fn router(app: Arc<AppConfig>) -> Router<Body, Infallible> {
|
||||
debug!("Building site router");
|
||||
Router::builder()
|
||||
.data(app)
|
||||
|
|
Loading…
Reference in New Issue