# #install_printer Yocaml.Path.pp ;;
# #install_printer Yocaml.Deps.pp ;;
open Yocaml
let www = Path.rel [ "_www" ] ;;
To start, download the file icons.svg into
your assets/images
directory. It contains several SVG icons (the
OCaml logo, the Creative Commons logo,
and an RSS/Atom logo) sourced from Simple
Icons.
The SVG file merges multiple symbols, making it easy to invoke icons using the
<use>
tag. This allows you to include an external SVG while keeping the ability to style it with CSS (which is not possible with the<img>
tag). Theicons
file may not be directly viewable in your browser, but rest assured, it is the file used for this site.
Copying a File
YOCaml provides a predefined action to simply copy a file into a given directory:
# Action.copy_file ;;
- : ?new_name:string -> into:Path.t -> Path.t -> Action.t = <fun>
First, we’ll create an action that can copy a single file. Later, we’ll see how to batch this action. We also create variables to help us more easily qualify the paths we’ll be using:
let assets = Path.rel [ "assets" ]
let images = Path.(assets / "images")
let copy_image image_path =
let images_path = Path.(www / "images") in
Action.copy_file ~into:images_path image_path
We can now modify our program
function to copy our icons.svg
file
by chaining the action we just created:
let program () =
let open Eff in
let cache = Path.(www / ".cache") in
Action.restore_cache cache
+ >>= copy_image Path.(images / "icons.svg")
>>= Action.store_cache cache
If we run our program using the command dune exec bin/blog.exe
, the
standard output should display the following logs:
[DEBUG]Cache restored from `./_www/.cache`
[DEBUG]`./_www/images/icons.svg` will be written
[INFO]`./_www/images/icons.svg` has been written
[DEBUG]Cache stored in `./_www/.cache`
If we immediately run the command again, the output should indicate
that the icons.svg
file is already up to date, ensuring that
minimality has been respected:
[DEBUG]Cache restored from `./_www/.cache`
[DEBUG]`./_www/images/icons.svg` is already up-to-date
[DEBUG]Cache stored in `./_www/.cache`
You can verify that everything works by modifying the source file or deleting the target to ensure that the file is only copied when necessary.
However, specifying each potential image manually is a bit tedious. We
would like to apply this action to all images in the assets/images
directory.
Batching Multiple Actions
The Batch module provides various functions to collapse a series of actions into a single one. Let’s focus on the following function:
# Yocaml.Batch.iter_files ;;
- : ?where:(Path.t -> bool) -> Path.t -> (Path.t -> Action.t) -> Action.t =
<fun>
This function will produce an action that goes through all files
in a directory (referenced by a path, as always) that satisfy the
where
predicate (if provided; otherwise, all child files are
considered valid) and applies an action to them. The function
itself is an action.
What we want to do is go through all the images in the assets/images
directory and apply our copy_image
function, which is exactly of
type Path.t -> Action.t
— perfect!
let copy_images =
Batch.iter_files
~where:(fun file ->
Path.has_extension "svg" file
|| Path.has_extension "png" file
|| Path.has_extension "jpg" file
|| Path.has_extension "gif" file)
images copy_image
If we inspect the type of copy_images
, we can see that the function
has the type Action.t
. We can therefore modify program
(and add
more images to assets/images
to verify that everything works):
let program () =
let open Eff in
let cache = Path.(www / ".cache") in
Action.restore_cache cache
+ >>= copy_image
>>= Action.store_cache cache
We have designed our first action, which simply moves files from a directory (only when necessary) while respecting a certain predicate. Before moving on, we can make a few minor adjustments!
Minor Improvements
Even though the code we wrote is perfectly valid, there are at least
two possible improvements. First, one might question the need to
separate the copy_image
and copy_images
actions. Indeed,
copy_image
is essentially an alias for copy_file
, so we could
rewrite copy_images
as follows:
let copy_images =
let images_path = Path.(www / "images") in
Batch.iter_files
~where:(fun file ->
Path.has_extension "svg" file
|| Path.has_extension "png" file
|| Path.has_extension "jpg" file
|| Path.has_extension "gif" file)
images
(Action.copy_file ~into:images_path)
A Utility Function
The second possible improvement concerns the way we check whether a path is an image. The code is quite redundant, and we might want to handle other files with multiple possible extensions (notably fonts). We could write a helper function that verifies whether a file has one of the extensions provided in a list:
let with_ext exts file =
List.exists (fun ext -> Path.has_extension ext file) exts
This allows us to rewrite our copy_images
action in a more concise
way (without losing readability):
let copy_images =
let images_path = Path.(www / "images")
and where = with_ext [ "svg"; "png"; "jpg"; "gif" ] in
Batch.iter_files
~where images
(Action.copy_file ~into:images_path)
And there you have it! We’ve coded our very first action and laid the groundwork for a static site generator! Awesome.
Conclusion
In this article, we reviewed how to use actions and batches of actions for very simple tasks. Implicitly, we also saw how well actions compose and how partial application can handle the cache so that, in the end, you can chain actions together in the main program.
In the next part, we’ll learn how to create files with YOCaml.