Module Yocaml

Here is the list of modules exposed by the YOCaml library. The concept behind this project is to offer, as a library, a set of tools to build pages. In other words, the end user, in this case me, would only have to build a new project in which Preface and YOCaml would be dependencies and then easily create his own static blog generator. It's likely that it won't be efficient or ergonomic, but it's a fun project to do in your spare time.

Please refer to the documentation index for an example.

Build system

Build is the main module of YOCaml. It is used to describe rules attached to dependencies. A static site generator is a collection of ordered rules. (So it is probably not useful to use this project and it would be better to write everything with make, sed and awk).

module Build : sig ... end

Description of a build rule.

module Deps : sig ... end

Deps is an attempt to represent (without much effort) a set of dependencies to build a file.

Handling

Effects Handling

In order to take advantage of Preface (for fun and profit) YOCaml describes a list of effects to manage. As for errors, executable effects are centralised.

module Effect : sig ... end

Centralization of the effects that can be performed.

Errors Handling

Errors handling is mainly based on a biased version of Result and Validation offered by Preface.

module Error : sig ... end

Describes the list of errors that can occur in the process of creating a blog.

module Try : sig ... end

A specialised version of Result (with Error.t as Error part). Try is very useful for producing sequential validations, where as soon as a step produces an error, the computation sequence is interrupted.

module Validate : sig ... end

A specialised version of Validation (with a nonempty list of Error.t as Invalid part). Validate is very useful to produce parallel validations, where each error is accumulated, as opposed to Try which stops the computation at the first error.

Metadata

When we generate pages statically, we often want to be able to attach metadata to them to give the documents more context.

module Date : sig ... end

Describes date (supposing naively that everything is in UTC).

module Metadata : sig ... end

Data structures attachable to articles/documents.

This module describes operations to query key-value objects abstractly. The Yocaml_yaml plugin is an example implementation of this protocol. (Or more precisely the module Yocaml.Key_value.Jsonm_object).

module Key_value : sig ... end

To attach additional content to documents, YOCaml uses a Metadata mechanism. Generally, the formats used for this kind of metadata (Yaml, JSON or even TOML) can be abstractly represented as abstract objects, like a key-value table.

RSS handling

Deploying RSS feeds should be standard in all blogs. This is why minimal support for RSS is possible without using a plugin.

module Rss : sig ... end

A minimalist RSS2 representation.

module Languages : sig ... end

Deal with languages identifiers.

module Mime : sig ... end

An almost exhaustive list of MIME types extracted from MDN: Common MIME types

Utils

Useful tools for developing a YOCaml generator.

module Filepath : sig ... end

As Wordpress is mainly used to move files, having some functions to work with file names can be a very good idea!

module Log : sig ... end

Describing log-level.

module Util : sig ... end

An invasive but probably useful tooling.

module Lexicon : sig ... end

A centralisation of feedback messages.

Runtime

A YOCaml program is a pure application that has, a priori, no dependence on the operating system. These dependencies are provided when a program is run (as declared in the yocaml_unix package).

module Runtime : sig ... end

Describes a dedicated runtime for varying the execution context of YOCaml.

Included general stuff

Included common util

There are always lots of little unreadable tools that I want to use... sometimes it improves readability... sometimes not.

include module type of Util

String util

As I was not very serious... strings occupy a very large place in Wordpress... so it is necessary to be able to work correctly with them.

val split_metadata : string -> string option * string

Infix operators

Even if sometimes, infix operators can seem unreadable... the immoderate use of Arrows has already made the code incomprehensible... so why deprive yourself?

val ($) : ('a -> 'b) -> 'a -> 'b

f $ x is f @@ x which is f x... but I don't like @@.

Working with file name

include module type of Filepath
val into : string -> string -> string

t |> into dir describes a t into a dir.

val with_extension : string -> string -> bool

with_extension ext path returns true if path ends with ext false otherwise, ie: with_extension "html" "index.html" returns true but with_extenstion "html" "foohtml" returns false.

val basename : string -> string

Keep the filename and remove the path.

val add_extension : string -> string -> string

Add an extension to a t. For example: add_extension "index.txt" "html" will produce "index.txt.html".

val remove_extension : string -> string

Remove the extension of a path.

val replace_extension : string -> string -> string

Replace the extension of a path.

Included Effect plumbery

A page generation process usually involves composing and executing effects, so rather than constantly forcing the Effect module into user space, the module is injected into the high-level API.

include module type of Effect

A bad faith preamble

To be beautiful and modern, this project separates the description of the programme from its interpretation. But as the composition is not really to my taste in Preface, I decided to centralize all the effects, like the errors, in one module.

Ugh, that sounds perfectly stupid... it would be like considering that you can only express one family of effects (you could call it ... IO). Don't panic, the first parameter of type effect allows you to make a selective choice when defining Freer. One could say that one takes advantage of the non-surjective aspect of the constructors of a sum (thanks to the GADTs!). Well, I'd be lying if I said I was convinced it was a good approach, but at least it seems viable.

Effects list

Boy, this type sounds like a hell of a lot of trouble to read! don't read it and go a little lower, there are kind of smarts constructors.

type (_, 'a) effects = (_, 'a) Effect.effects =
  1. | File_exists : Filepath.t -> (< file_exists : unit.. >, bool) effects
  2. | Target_exists : Filepath.t -> (< target_exists : unit.. >, bool) effects
  3. | Get_modification_time : Filepath.t -> (< get_modification_time : unit.. >, int Try.t) effects
  4. | Target_modification_time : Filepath.t -> (< target_modification_time : unit.. >, int Try.t) effects
  5. | Read_file : Filepath.t -> (< read_file : unit.. >, string Try.t) effects
  6. | Content_changes : (string * Filepath.t) -> (< content_changes : unit.. >, (string, unit) Either.t Try.t) effects
  7. | Write_file : (Filepath.t * string) -> (< write_file : unit.. >, unit Try.t) effects
  8. | Read_dir : (Filepath.t * [< `Files | `Directories | `Both ] * Filepath.t Preface.Predicate.t) -> (< read_dir : unit.. >, Filepath.t list) effects
  9. | Command : string -> (< command : unit.. >, int) effects
  10. | Log : (Log.level * string) -> (< log : unit.. >, unit) effects
  11. | Throw : Error.t -> (< throw : unit.. >, 'a) effects
  12. | Raise : exn -> (< raise_ : unit.. >, 'a) effects

Global definition

Complete mechanism for describing programs by description and providing them with handlers (interpreters/runtime) for all effects modelled in type t. (So absolutely not taking advantage of the slicing capability... It was well worth it!)

Freer monad over effects

All the plumbing for effects description/interpretation resides through a Freer monad (thanks Preface). Although this module is included below, I have taken the liberty of displaying it... for documentation purposes only.

module Freer = Effect.Freer

Performing effects

Once described (or/and specialised), the effects must be produced in a programme description. To transform the description of an effect (a value of type Effect.effect) into the execution of this effect, thus a value of type Effect.t), the perform function is used.

Filesystem

In generating a static blog, having control over the file system seems to be a minimum!

val file_exists : Filepath.t -> bool Freer.t

file_exists path should be interpreted as returning true if the file denoted by the file path path exists, false otherwise.

val target_exists : Filepath.t -> bool Freer.t

target_exists path should be interpreted as returning true if the file denoted by the file path path exists, false otherwise.

val get_modification_time : Filepath.t -> int Try.t Freer.t

get_modification_time path should be interpreted as returning, as an integer, the Unix time (mtime corresponding to the modification date of the file denoted by the file path path.

val target_modification_time : Filepath.t -> int Try.t Freer.t

target_modification_time path should be interpreted as returning, as an integer, the Unix time (mtime corresponding to the modification date of the file denoted by the file path path.

val read_file : Filepath.t -> string Try.t Freer.t

read_file path should be interpreted as trying to read the contents of the file denoted by the file path path. At the moment I'm using strings mainly out of laziness, and as I'll probably be the only user of this library... it doesn't matter!

val content_changes : Filepath.t -> string -> (string, unit) Either.t Try.t Freer.t

content_changes content filepath should be interpreted as trying to check if the content of the file is different from the given content. (In order to reduce the mtime modification)

val write_file : Filepath.t -> string -> unit Try.t Freer.t

write_file path content should be interpreted as trying to write content to the file denoted by the file path path. In my understanding of the system, the file will be completely overwritten if it already exists. Once again I am using strings, but this time it is not laziness, it is to be consistent with read_file.

val read_children : Filepath.t -> Filepath.t Preface.Predicate.t -> Filepath.t list Freer.t

Get a list of all children of a path.

val read_child_files : Filepath.t -> Filepath.t Preface.Predicate.t -> Filepath.t list Freer.t

Get a list of all child files of a path (exclude dirs).

val read_child_directories : Filepath.t -> Filepath.t Preface.Predicate.t -> Filepath.t list Freer.t

Get a list of all child directories of a path (exclude files).

val collect_children : Filepath.t list -> Filepath.t Preface.Predicate.t -> Filepath.t list Freer.t

Same of read_children but searching through a list of directories.

val collect_child_files : Filepath.t list -> Filepath.t Preface.Predicate.t -> Filepath.t list Freer.t

Same of read_child_files but searching through a list of directories.

val collect_child_directories : Filepath.t list -> Filepath.t Preface.Predicate.t -> Filepath.t list Freer.t

Same of read_child_directories but searching through a list of directories.

val process_files : Filepath.t list -> Filepath.t Preface.Predicate.t -> (Filepath.t -> unit Freer.t) -> unit Freer.t

process_files path predicate action performs sequentially action on each files which satisfies predicate.

val command : string -> int Freer.t

command cmd performs a shell commands and returns the exit code.

Logging

Even if it would be possible to limit our feedback with the user to simply returning an integer (El famoso Unix Return)... it would still be more convenient to display feedback to the user on the stage the program is in, right?

val log : Log.level -> string -> unit Freer.t

log level message should be interpreted as writing (probably to standard output) a message associated with a log level. To look good, the colour should change according to the log level, it would look more professional!

val trace : string -> unit Freer.t

trace message is an alias of log Aliases.Trace.

val debug : string -> unit Freer.t

debug message is an alias of log Aliases.Debug.

val info : string -> unit Freer.t

info message is an alias of log Aliases.Info.

val warning : string -> unit Freer.t

warning message is an alias of log Aliases.Warning.

val alert : string -> unit Freer.t

alert message is an alias of log Aliases.Alert.

Open bar

When we are in the context of an IO, ahem, effect execution, it's open bar, we can do whatever we want, like throwing exceptions galore!

val throw : Error.t -> 'a Freer.t

throw error should be interpreted as... "fire, fire, what to do using an Error!".

val raise_ : exn -> 'a Freer.t

raise_ exn should be interpreted as... "fire, fire, what to do using an exception!".

Effects composition

val sequence : 'a list Freer.t -> ('a -> 'b -> 'b Freer.t) -> 'b Freer.t -> 'b Freer.t

Collapses sequentially YOCaml program. sequence ps f p produces a program which performs p followed by f ps. A common usage is p |> sequences ps f.

Included Freer combinators

As mentioned above, the plumbing of program description and program handling is provided through a Freer Monad, a technique that aims to describe a free build over a Left Kan extension. Although the presence of slicing allows for the construction of specialised effects handlers, in the use case of this blog generator, the effects I propagate turn out to be exactly those I have described in my complete effects list. Coicindance, I don't think so!

It therefore seems logical (not to say ergonomic) to introduce the Freer interface in the toplevel of the Effect module. But as the interface is long and tiring to read, I place it at the end of the module!

module Infix = Effect.Infix
module Syntax = Effect.Syntax
include Preface.Specs.FREER_MONAD with type 'a f = 'a Freer.f and type 'a t = 'a Freer.t and module Infix := Freer.Infix and module Syntax := Freer.Syntax
type !'a0 f = 'a Freer.f
type !'a0 t = 'a Freer.t =
  1. | Return : 'b -> 'b t
  2. | Bind : 'c f * ('c -> 'd t) -> 'd t
type (!'a, !'b) handle = ('a -> 'b) -> 'a f -> 'b
type !'a handler = 'a Effect.handler = {
  1. handler : 'b. ('b, 'a) handle;
}
val perform : 'a f -> 'a t
val run : 'a handler -> 'a t -> 'a
module To_monad = Effect.To_monad
module Functor = Effect.Functor
module Applicative = Effect.Applicative
module Selective = Effect.Selective
module Monad = Effect.Monad
val bind : ('a -> 'b t) -> 'a t -> 'b t
val map : ('a -> 'b) -> 'a t -> 'b t
val join : 'a t t -> 'a t
val return : 'a -> 'a t
val compose_left_to_right : ('a -> 'b t) -> ('b -> 'c t) -> 'a -> 'c t
val compose_right_to_left : ('b -> 'c t) -> ('a -> 'b t) -> 'a -> 'c t
val lift : ('a -> 'b) -> 'a t -> 'b t
val lift2 : ('a -> 'b -> 'c) -> 'a t -> 'b t -> 'c t
val lift3 : ('a -> 'b -> 'c -> 'd) -> 'a t -> 'b t -> 'c t -> 'd t
val replace : 'a -> 'b t -> 'a t
val void : 'a t -> unit t
module Traverse = Effect.Traverse
include module type of Infix with type 'a t := 'a Freer.t
include Preface.Specs.Applicative.INFIX with type 'a t := 'a Yocaml__.Effect.Freer.t
val (<*>) : ('a -> 'b) Yocaml__.Effect.Freer.t -> 'a Yocaml__.Effect.Freer.t -> 'b Yocaml__.Effect.Freer.t
val (<**>) : 'a Yocaml__.Effect.Freer.t -> ('a -> 'b) Yocaml__.Effect.Freer.t -> 'b Yocaml__.Effect.Freer.t
val (*>) : unit Yocaml__.Effect.Freer.t -> 'a Yocaml__.Effect.Freer.t -> 'a Yocaml__.Effect.Freer.t
val (<*) : 'a Yocaml__.Effect.Freer.t -> unit Yocaml__.Effect.Freer.t -> 'a Yocaml__.Effect.Freer.t
include Preface.Specs.Monad.INFIX with type 'a t := 'a Yocaml__.Effect.Freer.t
val (=|<) : ('a -> 'b) -> 'a Yocaml__.Effect.Freer.t -> 'b Yocaml__.Effect.Freer.t
val (>|=) : 'a Yocaml__.Effect.Freer.t -> ('a -> 'b) -> 'b Yocaml__.Effect.Freer.t
val (>>=) : 'a Yocaml__.Effect.Freer.t -> ('a -> 'b Yocaml__.Effect.Freer.t) -> 'b Yocaml__.Effect.Freer.t
val (=<<) : ('a -> 'b Yocaml__.Effect.Freer.t) -> 'a Yocaml__.Effect.Freer.t -> 'b Yocaml__.Effect.Freer.t
val (>=>) : ('a -> 'b Yocaml__.Effect.Freer.t) -> ('b -> 'c Yocaml__.Effect.Freer.t) -> 'a -> 'c Yocaml__.Effect.Freer.t
val (<=<) : ('b -> 'c Yocaml__.Effect.Freer.t) -> ('a -> 'b Yocaml__.Effect.Freer.t) -> 'a -> 'c Yocaml__.Effect.Freer.t
val (>>) : unit Yocaml__.Effect.Freer.t -> 'b Yocaml__.Effect.Freer.t -> 'b Yocaml__.Effect.Freer.t
val (<<) : 'a Yocaml__.Effect.Freer.t -> unit Yocaml__.Effect.Freer.t -> 'a Yocaml__.Effect.Freer.t
val (<$>) : ('a -> 'b) -> 'a Yocaml__.Effect.Freer.t -> 'b Yocaml__.Effect.Freer.t
val (<&>) : 'a Yocaml__.Effect.Freer.t -> ('a -> 'b) -> 'b Yocaml__.Effect.Freer.t
val (<$) : 'a -> 'b Yocaml__.Effect.Freer.t -> 'a Yocaml__.Effect.Freer.t
val ($>) : 'a Yocaml__.Effect.Freer.t -> 'b -> 'b Yocaml__.Effect.Freer.t
include module type of Syntax with type 'a t := 'a Freer.t
include Preface.Specs.Applicative.SYNTAX with type 'a t := 'a Yocaml__.Effect.Freer.t
val and+ : 'a Yocaml__.Effect.Freer.t -> 'b Yocaml__.Effect.Freer.t -> ('a * 'b) Yocaml__.Effect.Freer.t
include Preface.Specs.Monad.SYNTAX with type 'a t := 'a Yocaml__.Effect.Freer.t
val let* : 'a Yocaml__.Effect.Freer.t -> ('a -> 'b Yocaml__.Effect.Freer.t) -> 'b Yocaml__.Effect.Freer.t
val let+ : 'a Yocaml__.Effect.Freer.t -> ('a -> 'b) -> 'b Yocaml__.Effect.Freer.t