Add more docs

This commit is contained in:
Mikko Ahlroth 2024-04-20 09:09:41 +03:00
parent 289d4d647d
commit 5f23dadde1
5 changed files with 121 additions and 24 deletions

View file

@ -2,7 +2,6 @@
//// parts of gloss, using the configuration to control what is done.
import gleam/result
import gloss/parser
import gloss/rendering/database as render_database
import gloss/config.{type Configuration}
import gloss/models/database.{type Database}
@ -10,7 +9,7 @@ import gloss/compiler.{type CompileDatabase}
/// Something failed when building the blog.
pub type BuildError {
ParseError(err: parser.ParseError)
ParseError(err: config.ParseError)
WriteError(err: config.WriteError)
}

View file

@ -1,3 +1,5 @@
//// The configuration is the main way to customize Gloss's behaviour.
import gleam/option
import gloss/paths.{type PathConfiguration}
import gloss/rendering/views.{
@ -5,17 +7,36 @@ import gloss/rendering/views.{
type SinglePostView,
}
import gloss/compiler.{type CompileDatabase, type Compiler}
import gloss/parser.{type Parser}
import gloss/models/database.{type Database}
import gloss/rendering/database.{type RenderDatabase} as _
/// An error occurred when writing the rendered results into files.
pub type WriteError {
WriteError(err: String)
}
/// An error occurred when parsing input files.
pub type ParseError {
ParseError(err: String)
}
/// The parser
pub type Parser =
fn() -> Result(Database, ParseError)
/// Renders the content of the database into HTML.
pub type Renderer =
fn(Database, CompileDatabase, Configuration) -> RenderDatabase
/// Writes the rendered HTML into files.
pub type Writer =
fn(RenderDatabase, Configuration) -> Result(Nil, WriteError)
/// View generators for the blog. These take the database and configuration and
/// must return a view function that is used for rendering.
///
/// See the `gloss/rendering/views` documentation for descriptions of the
/// individual views.
pub type Views {
Views(
base: fn(Database, Configuration) -> BaseView,
@ -28,31 +49,54 @@ pub type Views {
)
}
/// Compiling related configuration.
pub type Compiling {
Compiling(
/// The compiler to use for compiling the database.
database_compiler: fn(Database, Compiler) -> CompileDatabase,
/// The compiler to use for compiling individual items.
item_compiler: Compiler,
)
}
/// Rendering related configuration.
pub type Rendering {
Rendering(
renderer: fn(Database, CompileDatabase, Configuration) -> RenderDatabase,
/// The renderer to use.
renderer: Renderer,
/// The view generators to use.
views: Views,
/// The copyright statement of the blog, used in the footer and the feed.
copyright: String,
/// How many posts to show per page.
posts_per_page: Int,
/// How many posts to render in the feed.
posts_in_feed: Int,
)
}
/// Author related configuration.
pub type Author {
Author(name: String, email: option.Option(String), url: option.Option(String))
Author(
/// Name of the author.
name: String,
/// Email address of the author, used in the feed's author information.
email: option.Option(String),
/// Website URL of the author, used in the feed's author information and
/// added as a `rel="me"` link in the base layout.
url: option.Option(String),
)
}
pub type Configuration {
Configuration(
/// Name of the blog.
blog_name: String,
/// Absolute URL of the blog, this is where you are deploying it.
blog_url: String,
/// Language code of the blog, meaning the language of the content you are
/// writing. The language code must be according to
/// <https://datatracker.ietf.org/doc/html/rfc5646>.
language: String,
author: Author,
compiling: Compiling,
@ -60,6 +104,7 @@ pub type Configuration {
paths: PathConfiguration,
parser: Parser,
writer: Writer,
/// The folder to write the output into.
output_path: String,
)
}

View file

@ -1,3 +1,6 @@
//// This module contains default configurations that can be used to create a
//// blog quickly.
import gleam/uri
import gloss/config.{type Configuration, Compiling, Configuration, Rendering}
import gloss/rendering/views/single_post
@ -12,6 +15,7 @@ import gloss/parser
import gloss/writer
import gloss/compiler
/// View generators that use the Gloss builtin views.
const default_views = config.Views(
base: base.generate,
meta: meta.generate,
@ -22,6 +26,7 @@ const default_views = config.Views(
feed: feed.generate,
)
/// Get a sensible default configuration based on the given arguments.
pub fn default_config(
blog_name: String,
blog_url: String,

View file

@ -1,3 +1,5 @@
//// Parsing reads the input files into the database.
import gleam/result
import gleam/list
import gleam/regex
@ -8,28 +10,25 @@ import gloss/parser/common
import gloss/parser/post
import gloss/parser/page
import gloss/parser/menu
import gloss/config.{type ParseError, ParseError}
/// The default path where input files are read from.
const default_data_path = "./data"
pub type Parser =
fn() -> Result(Database, ParseError)
pub type ParseError {
FileError(path: String, err: simplifile.FileError)
PostParseError(filename: String, err: common.ParseError)
MenuParseError
}
/// The default parser.
pub fn default_parse() -> Result(Database, ParseError) {
parse_posts(database.new(), post_path())
|> result.try(parse_pages(_, page_path()))
|> result.try(parse_menu(_, menu_path()))
}
/// Parse posts from the given path into the database.
pub fn parse_posts(db: Database, path: String) -> Result(Database, ParseError) {
use filenames <- result.try(
simplifile.read_directory(path)
|> result.map_error(fn(err) { FileError(path, err) }),
|> result.map_error(fn(err) {
ParseError(path <> ": " <> string.inspect(err))
}),
)
let assert Ok(filename_regex) =
@ -49,11 +48,15 @@ pub fn parse_posts(db: Database, path: String) -> Result(Database, ParseError) {
list.map(filenames, fn(file) {
use contents <- result.try(
simplifile.read(path <> "/" <> file)
|> result.map_error(fn(err) { FileError(file, err) }),
|> result.map_error(fn(err) {
ParseError(file <> ": " <> string.inspect(err))
}),
)
post.parse(file, contents)
|> result.map_error(fn(err) { PostParseError(file, err) })
|> result.map_error(fn(err) {
ParseError(file <> ": " <> string.inspect(err))
})
}),
),
)
@ -61,10 +64,13 @@ pub fn parse_posts(db: Database, path: String) -> Result(Database, ParseError) {
Ok(list.fold(posts, db, database.add_post))
}
/// Parse pages from the given path into the database.
pub fn parse_pages(db: Database, path: String) -> Result(Database, ParseError) {
use filenames <- result.try(
simplifile.read_directory(path)
|> result.map_error(fn(err) { FileError(path, err) }),
|> result.map_error(fn(err) {
ParseError(path <> ": " <> string.inspect(err))
}),
)
use pages <- result.try(result.all(
@ -75,28 +81,37 @@ pub fn parse_pages(db: Database, path: String) -> Result(Database, ParseError) {
|> list.map(fn(file) {
use contents <- result.try(
simplifile.read(path <> "/" <> file)
|> result.map_error(fn(err) { FileError(file, err) }),
|> result.map_error(fn(err) {
ParseError(file <> ": " <> string.inspect(err))
}),
)
page.parse(file, contents)
|> result.map_error(fn(err) { PostParseError(file, err) })
|> result.map_error(fn(err) {
ParseError(file <> ": " <> string.inspect(err))
})
}),
))
Ok(list.fold(pages, db, database.add_page))
}
/// Parse the menu from the given file into the database.
pub fn parse_menu(db: Database, file: String) -> Result(Database, ParseError) {
case simplifile.verify_is_file(file) {
Ok(True) -> {
use contents <- result.try(
simplifile.read(file)
|> result.map_error(fn(err) { FileError(file, err) }),
|> result.map_error(fn(err) {
ParseError(file <> ": " <> string.inspect(err))
}),
)
use menu <- result.try(
menu.parse(contents)
|> result.replace_error(MenuParseError),
|> result.map_error(fn(err) {
ParseError("Menu parsing failed: " <> string.inspect(err))
}),
)
Ok(database.set_menu(db, menu))

View file

@ -1,3 +1,6 @@
//// Path configuration controls where files are generated and how links are
//// formed.
import gleam/int
import gleam/string
import gleam/list
@ -6,30 +9,55 @@ import gloss/models/page.{type Page}
import gloss/utils/date.{type Month}
import gloss/utils/ints/day
pub const default_root = ""
const default_root = ""
/// The default index path.
pub const default_index = "/index"
/// The default filename where the feed is written.
pub const default_feed_file = "/feed.xml"
/// The default path where the feed is accessible when hosted.
pub const default_feed = default_feed_file
/// The path configuration controls where files will be located and where links
/// will point to.
pub type PathConfiguration {
PathConfiguration(
/// The root path where the blog will be accessible. With starting slash but
/// without trailing slash, e.g. `/gloss_blog`. Note that if the blog is
/// accessible without a subpath, this value should be `""`.
root: String,
/// The index path. Note that the first page of the index is always written
/// into `"/"`, meaning `index.html`. This path is used for the rest of the
/// pages, e.g. `"/wibble"` would result in `/wibble/2.html`,
/// `/wibble/3.html` and so on.
index: String,
/// Path to a single post.
single_post: fn(Post) -> String,
/// Path to a page.
page: fn(Page) -> String,
/// Path to a tag archive.
tag: fn(String) -> String,
/// Path to a year archive.
year: fn(Int) -> String,
/// Path to a month archive of a given year.
month: fn(Int, Month) -> String,
/// List page path: given the original path such as `/tag/wibble` as a
/// string and the page number, forms the final path.
list_page: fn(String, Int) -> String,
/// HTML path: Append (or don't) the `.html` extension to the given path.
/// If you are using fancy URLs without extensions, override this to do
/// nothing.
html: fn(String) -> String,
/// Path to the feed as it is accessible from the browser.
feed: String,
/// Path and file name of the feed file that will be written.
feed_file: String,
)
}
/// Default path configuration.
pub const defaults = PathConfiguration(
root: default_root,
index: default_index,
@ -44,6 +72,7 @@ pub const defaults = PathConfiguration(
feed_file: default_feed_file,
)
/// Post path in the format `/2024/12/31/slug`.
pub fn default_single_post(post: Post) {
let post_date = post.get_date(post)
let date_parts =
@ -59,18 +88,22 @@ pub fn default_single_post(post: Post) {
"/" <> string.join(date_parts, "/") <> "/" <> post.slug
}
/// Page path in the format `/slug`.
pub fn default_page(page: Page) {
"/" <> page.slug
}
/// Tag path in the format `/tag/tag`.
pub fn default_tag(tag: String) {
"/tag/" <> tag
}
/// Year archive path in the format `/archive/2024`.
pub fn default_year_archive(year: Int) {
"/archive" <> "/" <> int.to_string(year)
}
/// Month archive path in the format `/archive/2024/05`.
pub fn default_month_archive(year: Int, month: Month) {
default_year_archive(year)
<> "/"
@ -87,7 +120,7 @@ pub fn default_list_page(path: String, page: Int) {
}
}
/// Get path with the .html extension
/// Get path with the .html extension.
pub fn default_html(path: String) {
path <> ".html"
}