Add some docs and rip out unused stuff
This commit is contained in:
parent
992d000fac
commit
dca56d1053
17 changed files with 104 additions and 51 deletions
|
@ -1,3 +1,6 @@
|
|||
//// The database contains all the parsed data from the input files, but not
|
||||
//// processed content.
|
||||
|
||||
import gleam/dict.{type Dict}
|
||||
import gleam/list
|
||||
import gleam/option.{None, Some}
|
||||
|
@ -9,6 +12,7 @@ import gloss/utils/date.{type Month}
|
|||
import gloss/utils/ordered_tree.{type OrderedTree}
|
||||
import gloss/utils/uniqid.{type Generator, type UniqID}
|
||||
|
||||
/// Internal post ID, generated automatically.
|
||||
pub type PostID =
|
||||
UniqID
|
||||
|
||||
|
@ -16,12 +20,15 @@ pub type PostWithID {
|
|||
PostWithID(id: PostID, post: Post)
|
||||
}
|
||||
|
||||
/// All tags and their posts.
|
||||
pub type TagPosts =
|
||||
Dict(Tag, OrderedTree(PostWithID))
|
||||
|
||||
/// Posts organised by month. This is used inside `YearPosts`.
|
||||
pub type MonthPosts =
|
||||
Dict(Month, OrderedTree(PostWithID))
|
||||
|
||||
/// Posts organised by year, containing posts organised by month.
|
||||
pub type YearPosts =
|
||||
Dict(Int, MonthPosts)
|
||||
|
||||
|
@ -37,6 +44,7 @@ pub opaque type Database {
|
|||
)
|
||||
}
|
||||
|
||||
/// Create a new empty database.
|
||||
pub fn new() -> Database {
|
||||
Database(
|
||||
posts: new_tree(),
|
||||
|
@ -49,6 +57,7 @@ pub fn new() -> Database {
|
|||
)
|
||||
}
|
||||
|
||||
/// Add a post into the database.
|
||||
pub fn add_post(db: Database, post: Post) -> Database {
|
||||
let post_date = post.get_date(post)
|
||||
let #(id, id_generator) = uniqid.get(db.id_generator)
|
||||
|
@ -100,22 +109,27 @@ pub fn add_post(db: Database, post: Post) -> Database {
|
|||
)
|
||||
}
|
||||
|
||||
/// Add a page into the database.
|
||||
pub fn add_page(db: Database, page: Page) -> Database {
|
||||
Database(..db, pages: [page, ..db.pages])
|
||||
}
|
||||
|
||||
/// Set the menu items of the database, replacing any old ones.
|
||||
pub fn set_menu(db: Database, menu: List(MenuItem)) -> Database {
|
||||
Database(..db, menu: menu)
|
||||
}
|
||||
|
||||
/// Get posts organised by tags.
|
||||
pub fn tags(db: Database) {
|
||||
db.tags
|
||||
}
|
||||
|
||||
/// Get posts organised by years and months.
|
||||
pub fn years(db: Database) {
|
||||
db.years
|
||||
}
|
||||
|
||||
/// Get all posts in the given order.
|
||||
pub fn get_posts_with_ids(
|
||||
db: Database,
|
||||
order: ordered_tree.WalkOrder,
|
||||
|
@ -123,10 +137,12 @@ pub fn get_posts_with_ids(
|
|||
ordered_tree.to_list(db.posts, order)
|
||||
}
|
||||
|
||||
/// Get pages.
|
||||
pub fn pages(db: Database) {
|
||||
db.pages
|
||||
}
|
||||
|
||||
/// Get menu items.
|
||||
pub fn menu(db: Database) {
|
||||
db.menu
|
||||
}
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
/// A post or page header: a string key and a string value.
|
||||
pub type Header =
|
||||
#(String, String)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/// A menu item that points to some URL (relative or absolute) and has a name.
|
||||
pub type MenuItem {
|
||||
MenuItem(url: String, name: String)
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//// A static page in the blog that is not part of any post lists or archives.
|
||||
|
||||
import gloss/models/header.{type Header}
|
||||
|
||||
pub type Page {
|
||||
|
|
|
@ -1,15 +1,22 @@
|
|||
//// A post in the blog.
|
||||
|
||||
import gleam/int
|
||||
import gleam/option.{type Option}
|
||||
import gleam/order.{type Order, Eq, Gt, Lt}
|
||||
import gleam/order.{type Order, Eq}
|
||||
import gloss/utils/date.{type Date}
|
||||
import gloss/utils/time.{type Time, Time}
|
||||
import gloss/utils/luxon.{type DateTime}
|
||||
import gloss/models/header.{type Header}
|
||||
|
||||
pub type PostedAt {
|
||||
/// The post only had date information.
|
||||
JustDate(Date)
|
||||
|
||||
/// The post had date, time, and timezone information.
|
||||
DateTime(date: Date, time: Time, tz: String, luxon: DateTime)
|
||||
}
|
||||
|
||||
/// A tag is any string of lowercase characters, numbers, dashes, or underscores.
|
||||
pub type Tag =
|
||||
String
|
||||
|
||||
|
@ -20,12 +27,15 @@ pub type Post {
|
|||
tags: List(Tag),
|
||||
headers: List(Header),
|
||||
content: String,
|
||||
/// The content before the split, if any
|
||||
short_content: Option(String),
|
||||
date: PostedAt,
|
||||
/// The post's order during that day, if there are multiple posts on the same day.
|
||||
order: Int,
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the date of the post.
|
||||
pub fn get_date(post: Post) -> Date {
|
||||
case post.date {
|
||||
JustDate(date) -> date
|
||||
|
@ -33,6 +43,7 @@ pub fn get_date(post: Post) -> Date {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get the time of the post, if it exists.
|
||||
pub fn get_time(post: Post) -> Option(Time) {
|
||||
case post.date {
|
||||
JustDate(..) -> option.None
|
||||
|
@ -40,6 +51,7 @@ pub fn get_time(post: Post) -> Option(Time) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get the Luxon datetime of the post, if it exists.
|
||||
pub fn get_luxon(post: Post) -> Option(luxon.DateTime) {
|
||||
case post.date {
|
||||
JustDate(..) -> option.None
|
||||
|
@ -47,21 +59,25 @@ pub fn get_luxon(post: Post) -> Option(luxon.DateTime) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Compare two posts and get an order between them.
|
||||
pub fn comparator(a: Post, b: Post) -> Order {
|
||||
let a_date = get_date(a)
|
||||
let b_date = get_date(b)
|
||||
|
||||
case date.compare(a_date, b_date) {
|
||||
Lt -> Lt
|
||||
Gt -> Gt
|
||||
Eq -> {
|
||||
let a_time = option.lazy_unwrap(get_time(a), time.nil_time)
|
||||
let b_time = option.lazy_unwrap(get_time(b), time.nil_time)
|
||||
time.compare(a_time, b_time)
|
||||
case time.compare(a_time, b_time) {
|
||||
Eq -> int.compare(a.order, b.order)
|
||||
other -> other
|
||||
}
|
||||
}
|
||||
other -> other
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the post's date or datetime formatted as an ISO 8601 formatted string.
|
||||
pub fn to_iso8601(post: Post) -> String {
|
||||
case post.date {
|
||||
JustDate(d) -> date.format_iso(d)
|
||||
|
|
|
@ -4,10 +4,12 @@ import gleam/list
|
|||
import gleam/bool
|
||||
import gloss/models/header.{type Header}
|
||||
|
||||
/// The default file extension for source files.
|
||||
pub const filename_postfix = ".md"
|
||||
|
||||
const header_separator = ":"
|
||||
|
||||
/// An error that occurred when parsing content.
|
||||
pub type ParseError {
|
||||
EmptyFile
|
||||
HeaderMissing
|
||||
|
@ -19,16 +21,19 @@ pub type ParseError {
|
|||
MalformedHeader(header: String)
|
||||
}
|
||||
|
||||
/// If the `value` is `Ok`, run `if_ok`, otherwise return `error`.
|
||||
pub fn try(value: Result(a, b), error: c, if_ok: fn(a) -> Result(d, c)) {
|
||||
result.try(result.replace_error(value, error), if_ok)
|
||||
}
|
||||
|
||||
/// Parse given lines as headers.
|
||||
pub fn parse_headers(headers: List(String)) -> Result(List(Header), ParseError) {
|
||||
headers
|
||||
|> list.map(parse_header)
|
||||
|> result.all()
|
||||
}
|
||||
|
||||
/// Parse given line as a header.
|
||||
pub fn parse_header(header: String) -> Result(Header, ParseError) {
|
||||
let header_parts = string.split(header, header_separator)
|
||||
let parts_amount = list.length(header_parts)
|
||||
|
|
|
@ -3,6 +3,7 @@ import gleam/string
|
|||
import gleam/result
|
||||
import gloss/models/menu.{MenuItem}
|
||||
|
||||
/// Parse the content and return menu items.
|
||||
pub fn parse(content: String) {
|
||||
string.split(content, "\n")
|
||||
|> list.filter(fn(line) { string.length(line) != 0 })
|
||||
|
|
|
@ -4,6 +4,7 @@ import gleam/list
|
|||
import gloss/models/page.{type Page, Page}
|
||||
import gloss/parser/common.{type ParseError, EmptyFile, HeaderMissing, try}
|
||||
|
||||
/// Parse page from file data.
|
||||
pub fn parse(filename: String, contents: String) -> Result(Page, ParseError) {
|
||||
let lines = string.split(contents, "\n")
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import gloss/parser/common.{
|
|||
MalformedFilename, MalformedHeader, MonthNotInt, YearNotInt, try,
|
||||
}
|
||||
|
||||
/// Post filenames must match this regex.
|
||||
pub const filename_regex = "^\\d{4}-\\d\\d-\\d\\d-.*\\.md$"
|
||||
|
||||
const filename_separator = "-"
|
||||
|
@ -28,6 +29,7 @@ type FilenameMeta {
|
|||
FilenameMeta(date: PostedAt, order: Int, slug: String)
|
||||
}
|
||||
|
||||
/// Parse post from file data.
|
||||
pub fn parse(filename: String, contents: String) -> Result(Post, ParseError) {
|
||||
let filename =
|
||||
string.slice(
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import gleam/int
|
||||
import gleam/string
|
||||
import gleam/list
|
||||
import gloss/models/post.{type Post}
|
||||
import gloss/models/page.{type Page}
|
||||
import gloss/paths/post as post_paths
|
||||
import gloss/utils/date.{type Month}
|
||||
import gloss/utils/ints/day
|
||||
|
||||
pub const default_root = ""
|
||||
|
||||
|
@ -44,9 +45,18 @@ pub const defaults = PathConfiguration(
|
|||
)
|
||||
|
||||
pub fn default_single_post(post: Post) {
|
||||
let post_path = post_paths.post_to_path(post)
|
||||
let post_date = post.get_date(post)
|
||||
let date_parts =
|
||||
list.map(
|
||||
[
|
||||
post_date.year,
|
||||
date.month_to_int(post_date.month),
|
||||
day.to_int(post_date.day),
|
||||
],
|
||||
pad_int,
|
||||
)
|
||||
|
||||
"/" <> post_path.date_path <> "/" <> post_path.slug
|
||||
"/" <> string.join(date_parts, "/") <> "/" <> post.slug
|
||||
}
|
||||
|
||||
pub fn default_page(page: Page) {
|
||||
|
@ -81,3 +91,9 @@ pub fn default_list_page(path: String, page: Int) {
|
|||
pub fn default_html(path: String) {
|
||||
path <> ".html"
|
||||
}
|
||||
|
||||
fn pad_int(number: Int) -> String {
|
||||
number
|
||||
|> int.to_string()
|
||||
|> string.pad_left(to: 2, with: "0")
|
||||
}
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
import gleam/string
|
||||
import gleam/int
|
||||
import gleam/list
|
||||
import gloss/models/post.{type Post}
|
||||
import gloss/utils/date
|
||||
import gloss/utils/ints/day
|
||||
|
||||
pub type PostPath {
|
||||
PostPath(date_path: String, slug: String)
|
||||
}
|
||||
|
||||
pub fn post_to_path(post: Post) -> PostPath {
|
||||
let post_date = post.get_date(post)
|
||||
let date_parts =
|
||||
list.map(
|
||||
[
|
||||
post_date.year,
|
||||
date.month_to_int(post_date.month),
|
||||
day.to_int(post_date.day),
|
||||
],
|
||||
pad_int,
|
||||
)
|
||||
|
||||
PostPath(date_path: string.join(date_parts, "/"), slug: post.slug)
|
||||
}
|
||||
|
||||
fn pad_int(number: Int) -> String {
|
||||
number
|
||||
|> int.to_string()
|
||||
|> string.pad_left(to: 2, with: "0")
|
||||
}
|
|
@ -55,9 +55,7 @@ pub fn render(
|
|||
let feed = render_feed(all_posts, compiled.posts, views)
|
||||
|
||||
RenderDatabase(
|
||||
orig: db,
|
||||
single_posts: posts,
|
||||
index: [],
|
||||
pages: pages,
|
||||
index_pages: index_pages,
|
||||
tag_pages: tag_pages,
|
||||
|
|
|
@ -1,35 +1,41 @@
|
|||
//// The render database stores the rendered posts and pages.
|
||||
|
||||
import gleam/dict.{type Dict}
|
||||
import lustre/element.{type Element}
|
||||
import gloss/models/database.{type Database, type PostID} as _
|
||||
import gloss/models/post.{type Post}
|
||||
import gloss/models/page.{type Page}
|
||||
import gloss/utils/date.{type Month}
|
||||
|
||||
pub type PostList =
|
||||
List(PostID)
|
||||
|
||||
/// A post and its rendered content.
|
||||
pub type RenderedSinglePost {
|
||||
RenderedSinglePost(orig: Post, content: Element(Nil))
|
||||
}
|
||||
|
||||
/// A page and its rendered content.
|
||||
pub type RenderedPage {
|
||||
RenderedPage(page: Page, content: Element(Nil))
|
||||
}
|
||||
|
||||
/// A list page's page number and the page's content.
|
||||
pub type RenderedListPage {
|
||||
ListPage(page: Int, content: Element(Nil))
|
||||
}
|
||||
|
||||
pub type RenderDatabase {
|
||||
RenderDatabase(
|
||||
orig: Database,
|
||||
/// Individual posts.
|
||||
single_posts: List(RenderedSinglePost),
|
||||
index: PostList,
|
||||
/// Individual pages.
|
||||
pages: List(RenderedPage),
|
||||
/// "Index" list pages, meaning the main post flow.
|
||||
index_pages: List(RenderedListPage),
|
||||
/// Tag list pages.
|
||||
tag_pages: Dict(String, List(RenderedListPage)),
|
||||
/// Year list pages.
|
||||
year_pages: Dict(Int, List(RenderedListPage)),
|
||||
/// Month list pages.
|
||||
month_pages: Dict(#(Int, Month), List(RenderedListPage)),
|
||||
/// The feed (corresponding to the main post flow).
|
||||
feed: Element(Nil),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,36 +3,57 @@ import gloss/compiler.{type CompiledPage, type CompiledPost}
|
|||
import gloss/models/post
|
||||
import gloss/models/page
|
||||
|
||||
/// The base view renders the base page layout.
|
||||
///
|
||||
/// The three arguments are:
|
||||
/// - The inner content to render in the layout.
|
||||
/// - Extra elements to put inside `<head>`.
|
||||
/// - Text to add as a prefix to the `<title>` element.
|
||||
pub type BaseView =
|
||||
fn(Element(Nil), List(Element(Nil)), String) -> Element(Nil)
|
||||
|
||||
/// Types of pages in the blog.
|
||||
pub type PageType {
|
||||
/// Individual post.
|
||||
Post(post.Post)
|
||||
/// Individual page.
|
||||
Page(page.Page)
|
||||
/// Any other page.
|
||||
Other(title: String, description: String)
|
||||
}
|
||||
|
||||
/// The meta view renders meta tags such as OpenGraph based on the page information.
|
||||
pub type MetaView =
|
||||
fn(PageType) -> List(Element(Nil))
|
||||
|
||||
/// View to render an individual post.
|
||||
pub type SinglePostView =
|
||||
fn(CompiledPost) -> Element(Nil)
|
||||
|
||||
/// View to render an individual page.
|
||||
pub type PageView =
|
||||
fn(CompiledPage) -> Element(Nil)
|
||||
|
||||
/// View to render the RSS or Atom feed.
|
||||
pub type FeedView =
|
||||
fn(List(CompiledPost)) -> Element(Nil)
|
||||
|
||||
/// Information passed for list pages.
|
||||
pub type ListInfo {
|
||||
ListInfo(
|
||||
/// The path to prefix before the page number. E.g. `/index` and the resulting path would then be `/index/2`.
|
||||
root_path: String,
|
||||
/// The page number of the current page being rendered.
|
||||
current_page: Int,
|
||||
/// The amount of pages in the current list.
|
||||
total_pages: Int,
|
||||
/// Posts on this page.
|
||||
posts: List(CompiledPost),
|
||||
/// Any extra content to put on top of the page before the posts.
|
||||
extra_header: Element(Nil),
|
||||
)
|
||||
}
|
||||
|
||||
/// View to render a list page. A list page is a page with many posts in a list.
|
||||
pub type ListPageView =
|
||||
fn(ListInfo) -> Element(Nil)
|
||||
|
|
|
@ -18,6 +18,7 @@ import gloss/config.{type Configuration}
|
|||
|
||||
const tag_min_size = 0.5
|
||||
|
||||
/// The base view pre-renders some content once, so that it can be reused for every render.
|
||||
pub type PreRendered {
|
||||
PreRendered(pages: Element(Nil), tags: Element(Nil), archives: Element(Nil))
|
||||
}
|
||||
|
|
|
@ -13,10 +13,12 @@ import gloss/utils/date
|
|||
import gloss/utils/time
|
||||
import gloss/utils/luxon
|
||||
|
||||
/// Generate a view that renders a full post.
|
||||
pub fn full_view(db: Database, config: Configuration) {
|
||||
view(_, True, db, config)
|
||||
}
|
||||
|
||||
/// Generate a view that renders a post that's inside of a list.
|
||||
pub fn list_view(db: Database, config: Configuration) {
|
||||
view(_, False, db, config)
|
||||
}
|
||||
|
|
|
@ -6,13 +6,8 @@ import lustre/ssg
|
|||
import lustre/ssg/xml
|
||||
import lustre/element
|
||||
import gloss/rendering/database.{type RenderDatabase} as _
|
||||
import gloss/models/post.{type Post}
|
||||
import gloss/paths/post.{type PostPath} as _
|
||||
import gloss/config.{type Configuration, WriteError}
|
||||
|
||||
pub type PostPathGenerator =
|
||||
fn(Post) -> PostPath
|
||||
|
||||
pub fn write(db: RenderDatabase, config: Configuration) {
|
||||
let site =
|
||||
ssg.new(config.output_path)
|
||||
|
|
Loading…
Reference in a new issue