summaryrefslogtreecommitdiffhomepage
path: root/gleam/src/ocda_write.gleam
diff options
context:
space:
mode:
Diffstat (limited to 'gleam/src/ocda_write.gleam')
-rw-r--r--gleam/src/ocda_write.gleam149
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
+ }
+ })
+ }
+ }
+ }
+}