From 5f23dadde102cd5c394b7d86c052e32a37e36abc Mon Sep 17 00:00:00 2001 From: Mikko Ahlroth Date: Sat, 20 Apr 2024 09:09:41 +0300 Subject: [PATCH] Add more docs --- src/gloss/builder.gleam | 3 +-- src/gloss/config.gleam | 51 +++++++++++++++++++++++++++++++++++++--- src/gloss/defaults.gleam | 5 ++++ src/gloss/parser.gleam | 49 ++++++++++++++++++++++++-------------- src/gloss/paths.gleam | 37 +++++++++++++++++++++++++++-- 5 files changed, 121 insertions(+), 24 deletions(-) diff --git a/src/gloss/builder.gleam b/src/gloss/builder.gleam index bd59055..dabb945 100644 --- a/src/gloss/builder.gleam +++ b/src/gloss/builder.gleam @@ -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) } diff --git a/src/gloss/config.gleam b/src/gloss/config.gleam index f93002a..14d847c 100644 --- a/src/gloss/config.gleam +++ b/src/gloss/config.gleam @@ -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 + /// . 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, ) } diff --git a/src/gloss/defaults.gleam b/src/gloss/defaults.gleam index 30f3a3d..ec962e9 100644 --- a/src/gloss/defaults.gleam +++ b/src/gloss/defaults.gleam @@ -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, diff --git a/src/gloss/parser.gleam b/src/gloss/parser.gleam index 1eddcf2..6f8b4a5 100644 --- a/src/gloss/parser.gleam +++ b/src/gloss/parser.gleam @@ -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)) diff --git a/src/gloss/paths.gleam b/src/gloss/paths.gleam index 25c491e..9c0fec3 100644 --- a/src/gloss/paths.gleam +++ b/src/gloss/paths.gleam @@ -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" }