Add docs and move stuff to internal

This commit is contained in:
Mikko Ahlroth 2024-04-19 23:48:49 +03:00
parent fd2a376387
commit 289d4d647d
26 changed files with 78 additions and 125 deletions

View file

@ -20,6 +20,7 @@ lustre_ssg = "~> 0.5.0"
gleam_javascript = "~> 0.8"
ranged_int = "~> 2.0"
bigi = "~> 3.0"
simplifile = "~> 1.7"
[dev-dependencies]
gleeunit = "~> 1.0"

View file

@ -27,3 +27,4 @@ gleeunit = { version = "~> 1.0" }
lustre = { version = "~> 4.1" }
lustre_ssg = { version = "~> 0.5.0" }
ranged_int = { version = "~> 2.0" }
simplifile = { version = "~> 1.7"}

View file

@ -1,3 +0,0 @@
export function to_string(buffer) {
return buffer.toString("utf8");
}

View file

@ -1,9 +0,0 @@
import { Ok, Error } from "./gleam.mjs";
export function resultify(callback) {
try {
return new Ok(callback());
} catch (err) {
return new Error(err);
}
}

View file

@ -1,5 +0,0 @@
import { mkdirSync } from "node:fs";
export function mkdirP(path) {
return mkdirSync(path, { recursive: true });
}

View file

@ -1,3 +1,6 @@
//// The builder contains convenience functions to interface with the different
//// parts of gloss, using the configuration to control what is done.
import gleam/result
import gloss/parser
import gloss/rendering/database as render_database
@ -5,24 +8,29 @@ import gloss/config.{type Configuration}
import gloss/models/database.{type Database}
import gloss/compiler.{type CompileDatabase}
/// Something failed when building the blog.
pub type BuildError {
ParseError(err: parser.ParseError)
WriteError(err: config.WriteError)
}
/// Parse the blog's input files.
pub fn parse(config: Configuration) {
config.parser()
|> result.map_error(ParseError)
}
/// Compile the post and page content into HTML strings.
pub fn compile(db: Database, config: Configuration) {
config.compiling.database_compiler(db, config.compiling.item_compiler)
}
/// Render the content into HTML pages.
pub fn render(db: Database, compiled: CompileDatabase, config: Configuration) {
config.rendering.renderer(db, compiled, config)
}
/// Write the render database into files.
pub fn write(posts: render_database.RenderDatabase, config: Configuration) {
config.writer(posts, config)
|> result.map_error(WriteError)

View file

@ -1,3 +1,6 @@
//// Compiling means turning post and page content into HTML. By default this
//// content is Markdown that is compiled with Marked.js.
import gleam/option
import gleam/dict.{type Dict}
import gleam/list
@ -7,29 +10,36 @@ import gloss/models/page.{type Page}
import gloss/models/database.{type Database, type PostID}
import gloss/utils/marked
/// Compiled post content: strings that contain HTML.
pub type PostContent {
PostContent(full: String, short: option.Option(String))
}
/// A post and its compiled content.
pub type CompiledPost {
CompiledPost(orig: Post, content: PostContent)
}
/// A page and its compiled content.
pub type CompiledPage {
CompiledPage(orig: Page, content: String)
}
/// A function to compile the given string content into an HTML string.
pub type Compiler =
fn(String, Database) -> String
/// Structure where the compilation results are stored.
pub type CompileDatabase {
CompileDatabase(posts: Dict(PostID, CompiledPost), pages: List(CompiledPage))
}
/// The default compiler that uses Marked.js with default settings.
pub fn default_compiler(content: String, _db: Database) {
marked.default_parse(content)
}
/// Compile contents of the database using the given compiler.
pub fn compile(db: Database, compiler: Compiler) {
CompileDatabase(
posts: compile_posts(db, compiler),
@ -37,6 +47,7 @@ pub fn compile(db: Database, compiler: Compiler) {
)
}
/// Compile all posts in the database using the given compiler.
pub fn compile_posts(
db: Database,
compiler: Compiler,
@ -59,6 +70,7 @@ pub fn compile_posts(
dict.from_list(posts)
}
/// Compile all pages in the database using the given compiler.
pub fn compile_pages(db: Database, compiler: Compiler) {
database.pages(db)
|> list.map(fn(page) {

View file

@ -0,0 +1,2 @@
@external(javascript, "../../../ffi_meta_url.mjs", "metaURL")
pub fn get() -> String

View file

@ -1,10 +1,10 @@
pub type Object
@external(javascript, "../../ffi_object.mjs", "create")
@external(javascript, "../../../ffi_object.mjs", "create")
pub fn new() -> Object
@external(javascript, "../../ffi_object.mjs", "set")
@external(javascript, "../../../ffi_object.mjs", "set")
pub fn set(object object: Object, prop prop: String, value value: a) -> Object
@external(javascript, "../../ffi_object.mjs", "get")
@external(javascript, "../../../ffi_object.mjs", "get")
pub fn get(object object: Object, prop prop: String) -> b

View file

@ -2,7 +2,7 @@ import gleam/result
import gleam/list
import gleam/regex
import gleam/string
import gloss/utils/fs
import simplifile
import gloss/models/database.{type Database}
import gloss/parser/common
import gloss/parser/post
@ -15,7 +15,7 @@ pub type Parser =
fn() -> Result(Database, ParseError)
pub type ParseError {
FileError(path: String, err: fs.FSError)
FileError(path: String, err: simplifile.FileError)
PostParseError(filename: String, err: common.ParseError)
MenuParseError
}
@ -28,7 +28,7 @@ pub fn default_parse() -> Result(Database, ParseError) {
pub fn parse_posts(db: Database, path: String) -> Result(Database, ParseError) {
use filenames <- result.try(
fs.readdir(path)
simplifile.read_directory(path)
|> result.map_error(fn(err) { FileError(path, err) }),
)
@ -48,7 +48,7 @@ pub fn parse_posts(db: Database, path: String) -> Result(Database, ParseError) {
result.all(
list.map(filenames, fn(file) {
use contents <- result.try(
fs.read_file(path <> "/" <> file)
simplifile.read(path <> "/" <> file)
|> result.map_error(fn(err) { FileError(file, err) }),
)
@ -63,7 +63,7 @@ pub fn parse_posts(db: Database, path: String) -> Result(Database, ParseError) {
pub fn parse_pages(db: Database, path: String) -> Result(Database, ParseError) {
use filenames <- result.try(
fs.readdir(path)
simplifile.read_directory(path)
|> result.map_error(fn(err) { FileError(path, err) }),
)
@ -74,7 +74,7 @@ pub fn parse_pages(db: Database, path: String) -> Result(Database, ParseError) {
})
|> list.map(fn(file) {
use contents <- result.try(
fs.read_file(path <> "/" <> file)
simplifile.read(path <> "/" <> file)
|> result.map_error(fn(err) { FileError(file, err) }),
)
@ -87,10 +87,10 @@ pub fn parse_pages(db: Database, path: String) -> Result(Database, ParseError) {
}
pub fn parse_menu(db: Database, file: String) -> Result(Database, ParseError) {
case fs.exists(file) {
case simplifile.verify_is_file(file) {
Ok(True) -> {
use contents <- result.try(
fs.read_file(file)
simplifile.read(file)
|> result.map_error(fn(err) { FileError(file, err) }),
)

View file

@ -1,4 +0,0 @@
pub type Buffer
@external(javascript, "../../ffi_buffer.mjs", "to_string")
pub fn to_string(buf buf: Buffer) -> String

View file

@ -19,14 +19,15 @@ pub type Month {
Dec
}
/// All months in order
/// All months in order.
pub const months = [Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
/// A date with 1-indexed years and days
/// A date with 1-indexed years and days.
pub type Date {
Date(year: Int, month: Month, day: Day)
}
/// Parse an integer into a month.
pub fn parse_month(month_int: Int) -> Result(Month, Nil) {
case month_int {
1 -> Ok(Jan)
@ -45,6 +46,7 @@ pub fn parse_month(month_int: Int) -> Result(Month, Nil) {
}
}
/// Get the number of days in a month in a given year.
pub fn days_in_month(month: Month, year: Int) {
case month {
Jan -> 31
@ -77,6 +79,7 @@ pub fn days_in_month(month: Month, year: Int) {
}
}
/// Check if a given date is valid.
pub fn is_valid_date(date: Date) -> Bool {
let day = day.to_int(date.day)
use <- bool.guard(day < 1, False)
@ -97,6 +100,7 @@ pub fn compare(a: Date, b: Date) -> Order {
}
}
/// Convert a month to a 1-indexed int.
pub fn month_to_int(month: Month) -> Int {
case month {
Jan -> 1
@ -114,6 +118,7 @@ pub fn month_to_int(month: Month) -> Int {
}
}
/// Convert a month to an English month name string.
pub fn month_to_string(month: Month) -> String {
case month {
Jan -> "January"
@ -131,6 +136,7 @@ pub fn month_to_string(month: Month) -> String {
}
}
/// Format a date in the format "2 Aug 2024".
pub fn format(date: Date) -> String {
int.to_string(day.to_int(date.day))
<> " "
@ -139,6 +145,7 @@ pub fn format(date: Date) -> String {
<> int.to_string(date.year)
}
/// Format a date in the ISO 8601 format.
pub fn format_iso(date: Date) -> String {
int.to_string(date.year)
<> "-"

View file

@ -1,10 +0,0 @@
/// A result (of type a) from an external function that can also raise an error
/// (of type b).
pub type CanRaise(a, b) {
CanRaise(value: a, error: b)
}
/// Convert a callback function (that should call an external function) from one
/// that can raise to one that will return a Result.
@external(javascript, "../../ffi_exceptions.mjs", "resultify")
pub fn resultify(callback callback: fn() -> CanRaise(a, b)) -> Result(a, b)

View file

@ -1,63 +0,0 @@
import gleam/result
import gleam/list
import gleam/javascript/array.{type Array}
import gloss/utils/buffer.{type Buffer}
import gloss/utils/exceptions.{type CanRaise}
pub type FSError
pub fn read_file(path: String) -> Result(String, FSError) {
use contents <- result.try(exceptions.resultify(fn() { do_read_file(path) }))
Ok(buffer.to_string(contents))
}
pub fn readdir(path: String) -> Result(List(String), FSError) {
use files <- result.try(exceptions.resultify(fn() { do_readdir(path) }))
Ok(list.map(array.to_list(files), buffer.to_string))
}
pub fn mkdir(path: String) -> Result(Nil, FSError) {
use _undefined <- result.try(exceptions.resultify(fn() { do_mkdir(path) }))
Ok(Nil)
}
pub fn mkdir_p(path: String) -> Result(String, FSError) {
use created <- result.try(exceptions.resultify(fn() { do_mkdir_p(path) }))
Ok(created)
}
pub fn exists(path: String) -> Result(Bool, FSError) {
use created <- result.try(exceptions.resultify(fn() { do_exists(path) }))
Ok(created)
}
pub fn write_file(path: String, data: String) -> Result(Nil, FSError) {
use _undefined <- result.try(
exceptions.resultify(fn() { do_write_file(path, data) }),
)
Ok(Nil)
}
@external(javascript, "fs", "readFileSync")
fn do_read_file(path: String) -> CanRaise(Buffer, FSError)
@external(javascript, "fs", "readdirSync")
fn do_readdir(path: String) -> CanRaise(Array(Buffer), FSError)
@external(javascript, "fs", "mkdirSync")
fn do_mkdir(path: String) -> CanRaise(Nil, FSError)
@external(javascript, "../../ffi_fs.mjs", "mkdirP")
fn do_mkdir_p(path: String) -> CanRaise(String, FSError)
@external(javascript, "fs", "existsSync")
fn do_exists(path: String) -> CanRaise(Bool, FSError)
@external(javascript, "fs", "writeFileSync")
fn do_write_file(path: String, data: String) -> CanRaise(Nil, FSError)

View file

@ -1,3 +1,5 @@
//// A day is a ranged integer from 1 to 31.
import bigi.{type BigInt}
import ranged_int/interface.{type Interface, Interface}

View file

@ -1,3 +1,5 @@
//// An hour is a ranged integer from 0 to 23.
import bigi.{type BigInt}
import ranged_int/interface.{type Interface, Interface}

View file

@ -1,3 +1,5 @@
//// A minute is a ranged integer from 0 to 59.
import bigi.{type BigInt}
import ranged_int/interface.{type Interface, Interface}

View file

@ -1,13 +1,18 @@
//// Bindings to the Luxon library.
import gloss/utils/date.{type Date}
import gloss/utils/time.{type Time}
/// Luxon DateTime.
pub type DateTime
/// Get a Luxon DateTime in the given timezone.
pub fn date_time_in_zone(date: Date, time: Time, tz: String) {
let datetime_str = date.format_iso(date) <> "T" <> time.format(time)
do_date_time_in_zone(datetime_str, tz)
}
/// Get the current time as UTC.
@external(javascript, "../../ffi_luxon.mjs", "utcNow")
pub fn utc_now() -> DateTime
@ -17,8 +22,6 @@ fn do_date_time_in_zone(
tz: String,
) -> Result(DateTime, Nil)
@external(javascript, "../../ffi_luxon.mjs", "toRFC2822")
pub fn to_rfc_2822(dt: DateTime) -> String
/// Format the DateTime as an ISO 8601 format string.
@external(javascript, "../../ffi_luxon.mjs", "toISO")
pub fn to_iso(dt: DateTime) -> String

View file

@ -1,24 +1,32 @@
import gloss/utils/object.{type Object}
//// Bindings to the marked.js library.
import gloss/internal/utils/object.{type Object}
/// Marked.js options object.
pub type Options =
Object
/// Create a new options object.
pub fn new_options() -> Options {
object.new()
}
/// Set the `mangle` option on or off.
pub fn set_mangle(options: Options, do_mangle: Bool) -> Options {
object.set(options, "mangle", do_mangle)
}
/// Set the `headerIds` option on or off.
pub fn set_header_ids(options: Options, ids: Bool) -> Options {
object.set(options, "headerIds", ids)
}
/// Set the `headerPrefix` option.
pub fn set_header_prefix(options: Options, prefix: String) -> Options {
object.set(options, "headerPrefix", prefix)
}
/// Parse a string containing Markdown using the default options.
pub fn default_parse(content: String) -> String {
let options =
new_options()
@ -29,5 +37,6 @@ pub fn default_parse(content: String) -> String {
parse(content, options)
}
/// Parse a string containing Markdown using Marked.js.
@external(javascript, "../../priv/vendor/marked.esm.mjs", "parse")
pub fn parse(content content: String, options options: Options) -> String

View file

@ -1,2 +0,0 @@
@external(javascript, "../../ffi_meta_url.mjs", "metaURL")
pub fn get() -> String

View file

@ -1,7 +1,8 @@
import gleam/uri
import gloss/utils/meta_url
import gloss/utils/path
import gloss/internal/utils/meta_url
import gloss/internal/utils/path
/// Get the path to the `priv` directory of `gloss`.
pub fn path() -> String {
let assert Ok(meta_url) = uri.parse(meta_url.get())

View file

@ -1,9 +0,0 @@
import gleam/string
/// Split the given string at the given index
pub fn split_at(str: String, index: Int) -> #(String, String) {
let len = string.length(str)
let first = string.slice(str, 0, index)
let rest = string.slice(str, index, len - index)
#(first, rest)
}

View file

@ -16,6 +16,7 @@ pub fn compare(a: Time, b: Time) -> Order {
}
}
/// Parse a time from an `hh:mm` format string.
pub fn parse(str: String) {
case string.split(str, ":") {
[hours, minutes] ->
@ -31,12 +32,14 @@ pub fn parse(str: String) {
}
}
/// Format a time to an `hh:mm` format string.
pub fn format(time: Time) -> String {
pad(int.to_string(hour.to_int(time.hours)))
<> ":"
<> pad(int.to_string(minute.to_int(time.minutes)))
}
/// Get a time at hour 0 and minute 0.
pub fn nil_time() {
let assert Ok(h) = hour.from_int(0)
let assert Ok(m) = minute.from_int(0)

View file

@ -1,16 +1,20 @@
import bigi.{type BigInt}
/// A unique ID.
pub type UniqID =
BigInt
/// Unique ID generator.
pub opaque type Generator {
Generator(id: BigInt)
}
/// Get a new generator.
pub fn new() -> Generator {
Generator(bigi.zero())
}
/// Get a unique ID and a new generator, that can be used to generate a new ID.
pub fn get(gen: Generator) -> #(UniqID, Generator) {
let new = bigi.add(gen.id, bigi.from_int(1))
#(new, Generator(new))

View file

@ -7,11 +7,12 @@ import lustre/ssg/xml
import lustre/element
import gloss/rendering/database.{type RenderDatabase} as _
import gloss/config.{type Configuration, WriteError}
import gloss/utils/priv
pub fn write(db: RenderDatabase, config: Configuration) {
let site =
ssg.new(config.output_path)
|> ssg.add_static_dir("./build/dev/javascript/gloss/priv/assets")
|> ssg.add_static_dir(priv.path() <> "/assets")
let single_posts =
db.single_posts