diff options
Diffstat (limited to 'gleam/src/ocda_write.gleam')
| -rw-r--r-- | gleam/src/ocda_write.gleam | 149 |
1 files changed, 149 insertions, 0 deletions
diff --git a/gleam/src/ocda_write.gleam b/gleam/src/ocda_write.gleam new file mode 100644 index 0000000..3c224fa --- /dev/null +++ b/gleam/src/ocda_write.gleam @@ -0,0 +1,149 @@ +//// ocda-write: a sample writer over the abstraction. +//// +//// ocda-write --text|--html <file>.ssp|<file>.ocda.db|<file>.zip [lang] +//// +//// The input may be either serialisation, and the writer is not told which. +//// That is the claim being demonstrated: a writer is a function from the +//// model to bytes, so a document renders the same whichever of its two +//// abstraction files it is given. +//// +//// Output goes to stdout, so that the comparison against spine's own output +//// is a shell pipeline and not a directory of files to clean up. + +import argv +import gleam/list +import gleam/string +import ocda/images +import gleam/io +import ocda/load +import simplifile +import write/epub +import write/html +import write/markdown +import write/text +import write/typst + +pub fn main() { + case argv.load().arguments { + ["--text", path] -> render(path, "", "text") + ["--text", path, lang] -> render(path, lang, "text") + ["--html", path] -> render(path, "", "html") + ["--html", path, lang] -> render(path, lang, "html") + ["--epub", path, "--out", out] -> write_epub(path, "", out) + ["--epub", path, lang, "--out", out] -> write_epub(path, lang, out) + ["--md", path] -> render_markdown(path, "", "image") + ["--md", path, lang] -> render_markdown(path, lang, "image") + ["--md", path, "--image-prefix", pth] -> render_markdown(path, "", pth) + ["--md", path, lang, "--image-prefix", pth] -> + render_markdown(path, lang, pth) + ["--typ", path] -> render(path, "", "typst") + ["--typ", path, lang] -> render(path, lang, "typst") + ["--typst", path] -> render(path, "", "typst") + ["--typst", path, lang] -> render(path, lang, "typst") + ["--typst", path, "--out", out] -> write_typst(path, "", out) + ["--typst", path, lang, "--out", out] -> write_typst(path, lang, out) + _ -> usage() + } +} + +fn usage() -> Nil { + io.println_error( + "usage: ocda-write --text|--html|--typst <file> [lang]\n" + <> " ocda-write --md <file> [lang] [--image-prefix <path>]\n" + <> " ocda-write --epub <file> [lang] --out <file>.epub", + ) + halt(2) +} + +fn render(path: String, lang: String, format: String) -> Nil { + case load.load(path, lang, False) { + Error(message) -> { + io.println_error("error: " <> message) + halt(2) + } + Ok(doc) -> + io.print(case format { + "html" -> html.write(doc) + "typst" -> typst.write(doc) + _ -> text.write(doc) + }) + } +} + +/// Markdown takes one thing the other formats do not: where the images are, +/// relative to wherever the .md is put. It has its own entry for that, as the +/// epub and the typst do for --out. +fn render_markdown(path: String, lang: String, image_pth: String) -> Nil { + case load.load(path, lang, False) { + Error(message) -> { + io.println_error("error: " <> message) + halt(2) + } + Ok(doc) -> io.print(markdown.write(doc, image_pth)) + } +} + +/// An epub is a zip, so it goes to a file rather than to stdout. +fn write_epub(path: String, lang: String, out: String) -> Nil { + case load.load(path, lang, False) { + Error(message) -> { + io.println_error("error: " <> message) + halt(2) + } + Ok(doc) -> + case simplifile.write_bits(out, epub.write(doc, path)) { + Ok(_) -> Nil + Error(e) -> { + io.println_error("error: " <> out <> ": " <> simplifile.describe_error(e)) + halt(2) + } + } + } +} + +/// Gleam has no standard exit, and the exit status is what a sweep script +/// reads. +@external(erlang, "erlang", "halt") +fn halt(code: Int) -> Nil + +/// A .typ names its images by path rather than carrying them, so when it goes +/// to a file the images are written beside it. +fn write_typst(path: String, lang: String, out: String) -> Nil { + case load.load(path, lang, False) { + Error(message) -> { + io.println_error("error: " <> message) + halt(2) + } + Ok(doc) -> { + case simplifile.write(out, typst.write(doc)) { + Ok(_) -> Nil + Error(e) -> { + io.println_error( + "error: " <> out <> ": " <> simplifile.describe_error(e), + ) + halt(2) + } + } + let dir = case string.split(out, "/") { + [_] -> "image" + parts -> + string.join(list.take(parts, list.length(parts) - 1), "/") <> "/image" + } + case images.can_give_images(path) { + False -> Nil + True -> + epub.images_named(doc) + |> list.each(fn(name) { + case images.image_bytes(path, name) { + Ok(bits) -> { + let _ = simplifile.create_directory_all(dir) + let _ = simplifile.write_bits(dir <> "/" <> name, bits) + Nil + } + Error(_) -> Nil + } + }) + } + } + } +} |
