Skip to content

Commit

Permalink
feat(app): render page index (#2)
Browse files Browse the repository at this point in the history
* feat(manifest): introduce out directory

* feat(manifest): introduce public directory

* feat(app): render app

* feat(app): embed layout

* feat(app): embed css in the output site

* chore(app): improve the default paths of manifest
  • Loading branch information
clearloop authored Dec 30, 2023
1 parent bac5f71 commit bca5f99
Show file tree
Hide file tree
Showing 12 changed files with 203 additions and 60 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
out
/target
.log
.DS_Store
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ handlebars = { version = "4.5.0", features = [ "dir_source" ] }
notify = "6.1.1"
pulldown-cmark = { version = "0.9.3", default-features = false }
serde = { version = "1.0.193", features = [ "derive" ] }
serde_json = "1.0.108"
serde_yaml = "0.9.29"
toml = "0.8.8"
tracing = "0.1.40"
2 changes: 2 additions & 0 deletions blog/cydonia.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ name = "Cydonia" # The name of the site.
# --------------------------------------

favicon = "favicon.ico" # The path to the favicon.ico.
out = "out" # The path to the output directory.
posts = "posts" # The path to the posts.
public = "public" # The path to the public directory.
templates = "templates" # The path to the templates.

# Theme could also be a folder:
Expand Down
8 changes: 6 additions & 2 deletions blog/templates/index.hbs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
<section>
{{#*inline "page"}}
<main>
<h1>{{ name }}</h1>
</main>
{{/inline}}

</section>
{{> layout }}
17 changes: 11 additions & 6 deletions blog/templates/layout.hbs
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
<html lang="en">
<head>
<title>{{ name }}</title>
<meta charset="utf-8">
<title>{{ title }}</title>
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="description" content="Empowering everyone to build reliable and efficient software.">
{{> headers }}
<meta name="description" content="Empowering everyone to build reliable and efficient software.">
{{#if index}}
<link rel="stylesheet" href="/index.css">
{{/if}}
{{#if post}}
<link rel="stylesheet" href="/post.css">
{{/if}}
</head>
<body>
{{> nav }}
{{ ~>page }}
{{> footer }}
{{> page }}
</body>
<footer>
</footer>
</html>
6 changes: 6 additions & 0 deletions blog/theme.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
html,
body {
min-height: 100%;
background-color: #000;
color: #fefefe;
}
79 changes: 69 additions & 10 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,81 @@
//! - theme.css
//! ```
use crate::Manifest;
use crate::{Manifest, Post, Theme};
use anyhow::Result;
use std::path::PathBuf;
use handlebars::Handlebars;
use std::{
fs::{self, File},
path::{Path, PathBuf},
};

/// The root of the site.
pub struct App {
/// The root path of the resources.
pub root: PathBuf,
/// The manifest of the site.
pub struct App<'app> {
/// The handlebars instance.
pub handlebars: Handlebars<'app>,
/// The cydonia.toml manifest.
pub manifest: Manifest,
/// The posts.
pub posts: Vec<Post>,
/// The theme.
pub theme: Theme,
}

impl App {
impl<'app> TryFrom<Manifest> for App<'app> {
type Error = anyhow::Error;

fn try_from(manifest: Manifest) -> Result<Self> {
Ok(Self {
handlebars: manifest.handlebars()?,
posts: manifest.posts()?,
theme: manifest.theme()?,
manifest,
})
}
}

impl<'app> App<'app> {
/// Create a new app.
pub fn new(root: PathBuf) -> Result<Self> {
let manifest = Manifest::load(&root)?;
Ok(Self { root, manifest })
pub fn load(root: PathBuf) -> Result<Self> {
Manifest::load(root)?.try_into()
}

/// Render the site.
pub fn render(&self) -> Result<()> {
fs::create_dir_all(&self.manifest.out)?;
self.manifest.copy_public()?;
self.render_css()?;
self.render_index()?;
Ok(())
}

/// Write css to the output directory.
pub fn render_css(&self) -> Result<()> {
fs::write(self.manifest.out.join("index.css"), &self.theme.index)?;
fs::write(self.manifest.out.join("post.css"), &self.theme.post).map_err(Into::into)
}

/// Render the index page.
pub fn render_index(&self) -> Result<()> {
self.render_template(
self.manifest.out.join("index.html"),
"index",
serde_json::json!({
"name": self.manifest.name,
"index": true,
}),
)
}

/// Render a template.
pub fn render_template(
&self,
name: impl AsRef<Path>,
template: &str,
data: serde_json::Value,
) -> Result<()> {
let file = File::create(self.manifest.out.join(name.as_ref()))?;
self.handlebars.render_to_write(template, &data, file)?;
Ok(())
}
}
78 changes: 63 additions & 15 deletions src/manifest.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! Manifest of the site.
use crate::{utils::Read, Post};
use crate::{
utils::{Prefix, Read},
Post, Theme,
};
use anyhow::Result;
use handlebars::Handlebars;
use serde::{Deserialize, Serialize};
Expand All @@ -16,39 +19,49 @@ pub struct Manifest {
pub name: String,

/// The path to the favicon.
favicon: Option<PathBuf>,
#[serde(default = "default::favicon")]
pub favicon: PathBuf,

/// The output directory.
#[serde(default = "default::out")]
pub out: PathBuf,

/// The path of the posts.
#[serde(default = "Manifest::default_posts")]
#[serde(default = "default::posts")]
pub posts: PathBuf,

/// The path of the public directory.
#[serde(default = "default::public")]
pub public: PathBuf,

/// The path of the templates.
#[serde(default = "Manifest::default_templates")]
#[serde(default = "default::templates")]
pub templates: PathBuf,

/// The path of the theme.
///
/// Could be a file or a directory.
#[serde(default = "Manifest::default_theme")]
#[serde(default = "default::theme")]
pub theme: PathBuf,
}

impl Manifest {
/// Load manifest from the provided path.
pub fn load(root: impl AsRef<Path>) -> Result<Self> {
let path = root.as_ref().join("cydonia.toml");
let mut manifest: Self = toml::from_str(&root.as_ref().join("cydonia.toml").read()?)
let manifest: Self = toml::from_str(&root.as_ref().join("cydonia.toml").read()?)
.map_err(|e| anyhow::anyhow!("Failed to parse {}: {}", path.display(), e))?;

if manifest.posts.is_relative() {
manifest.posts = root.as_ref().join(&manifest.posts);
}
Ok(manifest.abs(root))
}

if manifest.templates.is_relative() {
manifest.templates = root.as_ref().join(&manifest.templates);
/// Copy the public directory.
pub fn copy_public(&self) -> Result<()> {
if self.public.exists() {
std::fs::copy(&self.public, self.out.join("public"))?;
}

Ok(manifest)
Ok(())
}

/// Get the posts.
Expand All @@ -67,18 +80,53 @@ impl Manifest {
Ok(handlebars)
}

/// Get the theme.
pub fn theme(&self) -> Result<Theme> {
Theme::load(&self.theme)
}

/// Make paths absolute.
fn abs(mut self, prefix: impl AsRef<Path>) -> Self {
self.favicon.prefix(&prefix);
self.out.prefix(&prefix);
self.posts.prefix(&prefix);
self.public.prefix(&prefix);
self.templates.prefix(&prefix);
self.theme.prefix(&prefix);
self
}
}

mod default {
use std::{fs, path::PathBuf};

/// Default implementation of the out directory.
pub fn favicon() -> PathBuf {
fs::canonicalize(PathBuf::from("favicon")).unwrap_or_default()
}

/// Default implementation of the out directory.
pub fn out() -> PathBuf {
fs::canonicalize(PathBuf::from("out")).unwrap_or_default()
}

/// Default implementation of the posts.
pub fn default_posts() -> PathBuf {
pub fn posts() -> PathBuf {
fs::canonicalize(PathBuf::from("posts")).unwrap_or_default()
}

/// Default implementation of the posts.
pub fn public() -> PathBuf {
fs::canonicalize(PathBuf::from("public")).unwrap_or_default()
}

/// Default implementation of the templates.
pub fn default_templates() -> PathBuf {
pub fn templates() -> PathBuf {
fs::canonicalize(PathBuf::from("templates")).unwrap_or_default()
}

/// Default implementation of the templates.
pub fn default_theme() -> PathBuf {
pub fn theme() -> PathBuf {
fs::canonicalize(PathBuf::from("theme.css")).unwrap_or_default()
}
}
23 changes: 15 additions & 8 deletions src/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
use crate::utils::Read;
use anyhow::Result;
use std::path::PathBuf;
use std::path::Path;

/// The theme for the site.
#[derive(Debug, Clone)]
#[derive(Debug, Default, Clone)]
pub struct Theme {
/// Styles for the index page.
pub index: String,
Expand All @@ -15,8 +15,11 @@ pub struct Theme {

impl Theme {
/// Loads theme from the given path.
pub fn load(path: PathBuf) -> Result<Self> {
if path.is_file() {
pub fn load(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
if !path.exists() {
Ok(Default::default())
} else if path.is_file() {
let theme = path.read()?;

Ok(Self {
Expand All @@ -25,10 +28,14 @@ impl Theme {
})
} else {
let theme = path.join("theme.css").read().unwrap_or_default();

let index = [theme.clone(), path.join("index.css").read()?].concat();
let post = [theme, path.join("post.css").read()?].concat();
Ok(Self { index, post })
Ok(Self {
index: [
theme.clone(),
path.join("index.css").read().unwrap_or_default(),
]
.concat(),
post: [theme, path.join("post.css").read().unwrap_or_default()].concat(),
})
}
}
}
23 changes: 19 additions & 4 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
use anyhow::Result;
use colored::Colorize;
use std::path::Path;
use std::path::{Path, PathBuf};

/// A trait for reading file with full error info.
pub trait Read {
pub trait Read: Sized {
/// Read self to string with proper error info.
fn read(&self) -> Result<String>;
}

Expand All @@ -17,10 +18,24 @@ where
let path = self.as_ref();
std::fs::read_to_string(path).map_err(|e| {
anyhow::anyhow!(
"Failed to read file: {}, error: {}",
path.display().to_string().dimmed().underline(),
"Failed to read file: {}, {}",
path.display().to_string().underline(),
e.to_string()
)
})
}
}

/// Extension trait for `PathBuf`.
pub trait Prefix {
/// Prefix self with another path.
fn prefix(&mut self, prefix: impl AsRef<Path>);
}

impl Prefix for PathBuf {
fn prefix(&mut self, prefix: impl AsRef<Path>) {
if self.is_relative() {
*self = prefix.as_ref().join(&self)
}
}
}
Loading

0 comments on commit bca5f99

Please sign in to comment.