Add some docs and rip out unused stuff

This commit is contained in:
Mikko Ahlroth 2024-04-14 21:14:55 +03:00
parent 992d000fac
commit dca56d1053
17 changed files with 104 additions and 51 deletions

View file

@ -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
}

View file

@ -1,2 +1,3 @@
/// A post or page header: a string key and a string value.
pub type Header =
#(String, String)

View file

@ -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)
}

View file

@ -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 {

View file

@ -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)

View file

@ -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)

View file

@ -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 })

View file

@ -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")

View file

@ -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(

View file

@ -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")
}

View file

@ -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")
}

View file

@ -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,

View file

@ -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),
)
}

View file

@ -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)

View file

@ -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))
}

View file

@ -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)
}

View file

@ -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)