diff options
Diffstat (limited to 'gleam/src/ocda/inventory.gleam')
| -rw-r--r-- | gleam/src/ocda/inventory.gleam | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/gleam/src/ocda/inventory.gleam b/gleam/src/ocda/inventory.gleam new file mode 100644 index 0000000..6047313 --- /dev/null +++ b/gleam/src/ocda/inventory.gleam @@ -0,0 +1,138 @@ +//// What a source holds, beyond the one document the model is loaded from. +//// +//// The model is one language of one document. Two questions a reader asks +//// are about the file instead: which languages it holds, and, for a +//// database, what else it carries. They are here rather than in a reader +//// because the answer depends on the input form, which is the library's to +//// know, and so that a reader asking them does not learn which form it was +//// handed. A mirror of the D library's `ocda.inventory`. +//// +//// `languages` answers for every form, and answers the same: the languages +//// in the order the manifest gave them (which every `.ssp` records, in +//// `@source languages`), each with the markup file it was made from and the +//// sha256 of its `.ssp`. The database stores that digest; for a `.ssp` and a +//// pod it is taken from the bytes, which are the same bytes. +//// +//// `carried` is the database's: a `.ocda.db` holds the markup, images, +//// configuration and catalogues it was built from, a `.ssp` holds none of +//// them, and a pod's are its archive entries rather than anything the +//// abstraction says. + +import gleam/bit_array +import gleam/dict +import gleam/list +import gleam/result +import gleam/string +import ocda/load +import ocda/load_db +import ocda/load_pod +import ocda/load_ssp +import ocda/zip +import simplifile + +pub type Language { + Language(lang: String, source_filename: String, ssp_digest: String) +} + +pub type Carried { + Carried( + role: String, + compression: String, + count: String, + bytes: String, + stored: String, + ) +} + +pub fn languages(path: String) -> Result(List(Language), String) { + case load.source_of(path) { + load.OcdaDb -> { + use held <- result.try(load_db.languages_of_file(path)) + Ok( + list.map(held, fn(l) { + Language(l.lang, l.source_filename, l.ssp_digest) + }), + ) + } + load.Ssp -> { + use bytes <- result.try( + simplifile.read_bits(path) + |> result.map_error(fn(e) { path <> ": " <> simplifile.describe_error(e) }), + ) + use text <- result.try( + bit_array.to_string(bytes) + |> result.replace_error(path <> ": not valid UTF-8"), + ) + let doc = load_ssp.parse(text) + Ok([ + Language( + source_value(doc.source, "language"), + doc.source_filename, + sha256_hex(bytes), + ), + ]) + } + load.Pod -> { + use archive <- result.try(load_pod.archive_bytes(path)) + use entries <- result.try(zip.entries(archive)) + use found <- result.try( + zip.find_suffix(entries, ".ssp") + |> list.try_map(fn(e) { + use bytes <- result.try(zip.read(archive, e)) + use text <- result.try( + bit_array.to_string(bytes) + |> result.replace_error(e.name <> ": not valid UTF-8"), + ) + let doc = load_ssp.parse(text) + let lang = result.unwrap(load_pod.lang_of(e.name), "") + Ok(#( + Language(lang, doc.source_filename, sha256_hex(bytes)), + string.split(source_value(doc.source, "languages"), " "), + )) + }), + ) + let order = case found { + [#(_, o), ..] -> list.filter(o, fn(l) { l != "" }) + [] -> [] + } + let by_lang = list.map(found, fn(f) { #({ f.0 }.lang, f.0) }) |> dict.from_list + // the manifest's order, and anything it did not name after it + let named = list.filter_map(order, fn(l) { dict.get(by_lang, l) }) + let rest = + list.filter(found, fn(f) { !list.contains(order, { f.0 }.lang) }) + |> list.map(fn(f) { f.0 }) + Ok(list.append(named, rest)) + } + load.Unknown -> Error(path <> ": " <> load.source_name(load.Unknown)) + } +} + +/// Only a `.ocda.db` carries files. +pub fn is_carrier(path: String) -> Bool { + load.source_of(path) == load.OcdaDb +} + +pub fn carried(path: String) -> Result(List(Carried), String) { + case is_carrier(path) { + False -> Ok([]) + True -> { + use rows <- result.try(load_db.carried_of_file(path)) + Ok( + list.filter_map(rows, fn(r) { + case r { + [role, compression, n, bytes, stored] -> + Ok(Carried(role, compression, n, bytes, stored)) + _ -> Error(Nil) + } + }), + ) + } + } +} + +fn source_value(source: dict.Dict(String, String), key: String) -> String { + result.unwrap(dict.get(source, key), "") +} + +@external(erlang, "ocda_ffi", "sha256_hex") +fn sha256_hex(bytes: BitArray) -> String |
