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. //// parts of gloss, using the configuration to control what is done.
import gleam/result import gleam/result
import gloss/parser
import gloss/rendering/database as render_database import gloss/rendering/database as render_database
import gloss/config.{type Configuration} import gloss/config.{type Configuration}
import gloss/models/database.{type Database} import gloss/models/database.{type Database}
@ -10,7 +9,7 @@ import gloss/compiler.{type CompileDatabase}
/// Something failed when building the blog. /// Something failed when building the blog.
pub type BuildError { pub type BuildError {
ParseError(err: parser.ParseError) ParseError(err: config.ParseError)
WriteError(err: config.WriteError) 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 gleam/option
import gloss/paths.{type PathConfiguration} import gloss/paths.{type PathConfiguration}
import gloss/rendering/views.{ import gloss/rendering/views.{
@ -5,17 +7,36 @@ import gloss/rendering/views.{
type SinglePostView, type SinglePostView,
} }
import gloss/compiler.{type CompileDatabase, type Compiler} import gloss/compiler.{type CompileDatabase, type Compiler}
import gloss/parser.{type Parser}
import gloss/models/database.{type Database} import gloss/models/database.{type Database}
import gloss/rendering/database.{type RenderDatabase} as _ import gloss/rendering/database.{type RenderDatabase} as _
/// An error occurred when writing the rendered results into files.
pub type WriteError { pub type WriteError {
WriteError(err: String) 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 = pub type Writer =
fn(RenderDatabase, Configuration) -> Result(Nil, WriteError) 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 { pub type Views {
Views( Views(
base: fn(Database, Configuration) -> BaseView, base: fn(Database, Configuration) -> BaseView,
@ -28,31 +49,54 @@ pub type Views {
) )
} }
/// Compiling related configuration.
pub type Compiling { pub type Compiling {
Compiling( Compiling(
/// The compiler to use for compiling the database.
database_compiler: fn(Database, Compiler) -> CompileDatabase, database_compiler: fn(Database, Compiler) -> CompileDatabase,
/// The compiler to use for compiling individual items.
item_compiler: Compiler, item_compiler: Compiler,
) )
} }
/// Rendering related configuration.
pub type Rendering { pub type Rendering {
Rendering( Rendering(
renderer: fn(Database, CompileDatabase, Configuration) -> RenderDatabase, /// The renderer to use.
renderer: Renderer,
/// The view generators to use.
views: Views, views: Views,
/// The copyright statement of the blog, used in the footer and the feed.
copyright: String, copyright: String,
/// How many posts to show per page.
posts_per_page: Int, posts_per_page: Int,
/// How many posts to render in the feed.
posts_in_feed: Int, posts_in_feed: Int,
) )
} }
/// Author related configuration.
pub type Author { 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 { pub type Configuration {
Configuration( Configuration(
/// Name of the blog.
blog_name: String, blog_name: String,
/// Absolute URL of the blog, this is where you are deploying it.
blog_url: String, 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, language: String,
author: Author, author: Author,
compiling: Compiling, compiling: Compiling,
@ -60,6 +104,7 @@ pub type Configuration {
paths: PathConfiguration, paths: PathConfiguration,
parser: Parser, parser: Parser,
writer: Writer, writer: Writer,
/// The folder to write the output into.
output_path: String, 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 gleam/uri
import gloss/config.{type Configuration, Compiling, Configuration, Rendering} import gloss/config.{type Configuration, Compiling, Configuration, Rendering}
import gloss/rendering/views/single_post import gloss/rendering/views/single_post
@ -12,6 +15,7 @@ import gloss/parser
import gloss/writer import gloss/writer
import gloss/compiler import gloss/compiler
/// View generators that use the Gloss builtin views.
const default_views = config.Views( const default_views = config.Views(
base: base.generate, base: base.generate,
meta: meta.generate, meta: meta.generate,
@ -22,6 +26,7 @@ const default_views = config.Views(
feed: feed.generate, feed: feed.generate,
) )
/// Get a sensible default configuration based on the given arguments.
pub fn default_config( pub fn default_config(
blog_name: String, blog_name: String,
blog_url: String, blog_url: String,

View file

@ -1,3 +1,5 @@
//// Parsing reads the input files into the database.
import gleam/result import gleam/result
import gleam/list import gleam/list
import gleam/regex import gleam/regex
@ -8,28 +10,25 @@ import gloss/parser/common
import gloss/parser/post import gloss/parser/post
import gloss/parser/page import gloss/parser/page
import gloss/parser/menu import gloss/parser/menu
import gloss/config.{type ParseError, ParseError}
/// The default path where input files are read from.
const default_data_path = "./data" const default_data_path = "./data"
pub type Parser = /// The default parser.
fn() -> Result(Database, ParseError)
pub type ParseError {
FileError(path: String, err: simplifile.FileError)
PostParseError(filename: String, err: common.ParseError)
MenuParseError
}
pub fn default_parse() -> Result(Database, ParseError) { pub fn default_parse() -> Result(Database, ParseError) {
parse_posts(database.new(), post_path()) parse_posts(database.new(), post_path())
|> result.try(parse_pages(_, page_path())) |> result.try(parse_pages(_, page_path()))
|> result.try(parse_menu(_, menu_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) { pub fn parse_posts(db: Database, path: String) -> Result(Database, ParseError) {
use filenames <- result.try( use filenames <- result.try(
simplifile.read_directory(path) 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) = 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) { list.map(filenames, fn(file) {
use contents <- result.try( use contents <- result.try(
simplifile.read(path <> "/" <> file) 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) 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)) 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) { pub fn parse_pages(db: Database, path: String) -> Result(Database, ParseError) {
use filenames <- result.try( use filenames <- result.try(
simplifile.read_directory(path) 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( 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) { |> list.map(fn(file) {
use contents <- result.try( use contents <- result.try(
simplifile.read(path <> "/" <> file) 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) 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)) 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) { pub fn parse_menu(db: Database, file: String) -> Result(Database, ParseError) {
case simplifile.verify_is_file(file) { case simplifile.verify_is_file(file) {
Ok(True) -> { Ok(True) -> {
use contents <- result.try( use contents <- result.try(
simplifile.read(file) 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( use menu <- result.try(
menu.parse(contents) 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)) 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/int
import gleam/string import gleam/string
import gleam/list import gleam/list
@ -6,30 +9,55 @@ import gloss/models/page.{type Page}
import gloss/utils/date.{type Month} import gloss/utils/date.{type Month}
import gloss/utils/ints/day import gloss/utils/ints/day
pub const default_root = "" const default_root = ""
/// The default index path.
pub const default_index = "/index" pub const default_index = "/index"
/// The default filename where the feed is written.
pub const default_feed_file = "/feed.xml" pub const default_feed_file = "/feed.xml"
/// The default path where the feed is accessible when hosted.
pub const default_feed = default_feed_file 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 { pub type PathConfiguration {
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, 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, index: String,
/// Path to a single post.
single_post: fn(Post) -> String, single_post: fn(Post) -> String,
/// Path to a page.
page: fn(Page) -> String, page: fn(Page) -> String,
/// Path to a tag archive.
tag: fn(String) -> String, tag: fn(String) -> String,
/// Path to a year archive.
year: fn(Int) -> String, year: fn(Int) -> String,
/// Path to a month archive of a given year.
month: fn(Int, Month) -> String, 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, 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, html: fn(String) -> String,
/// Path to the feed as it is accessible from the browser.
feed: String, feed: String,
/// Path and file name of the feed file that will be written.
feed_file: String, feed_file: String,
) )
} }
/// Default path configuration.
pub const defaults = PathConfiguration( pub const defaults = PathConfiguration(
root: default_root, root: default_root,
index: default_index, index: default_index,
@ -44,6 +72,7 @@ pub const defaults = PathConfiguration(
feed_file: default_feed_file, feed_file: default_feed_file,
) )
/// Post path in the format `/2024/12/31/slug`.
pub fn default_single_post(post: Post) { pub fn default_single_post(post: Post) {
let post_date = post.get_date(post) let post_date = post.get_date(post)
let date_parts = let date_parts =
@ -59,18 +88,22 @@ pub fn default_single_post(post: Post) {
"/" <> string.join(date_parts, "/") <> "/" <> post.slug "/" <> string.join(date_parts, "/") <> "/" <> post.slug
} }
/// Page path in the format `/slug`.
pub fn default_page(page: Page) { pub fn default_page(page: Page) {
"/" <> page.slug "/" <> page.slug
} }
/// Tag path in the format `/tag/tag`.
pub fn default_tag(tag: String) { pub fn default_tag(tag: String) {
"/tag/" <> tag "/tag/" <> tag
} }
/// Year archive path in the format `/archive/2024`.
pub fn default_year_archive(year: Int) { pub fn default_year_archive(year: Int) {
"/archive" <> "/" <> int.to_string(year) "/archive" <> "/" <> int.to_string(year)
} }
/// Month archive path in the format `/archive/2024/05`.
pub fn default_month_archive(year: Int, month: Month) { pub fn default_month_archive(year: Int, month: Month) {
default_year_archive(year) 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) { pub fn default_html(path: String) {
path <> ".html" path <> ".html"
} }