123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371 |
- local datafile = require "datafile"
- local dir = require "pl.dir"
- local etlua = require "etlua"
- local plpath = require "pl.path"
- local pp = require "pl.pretty"
- local nsm = require "volksdata.namespace"
- local term = require "volksdata.term"
- local triple = require "volksdata.triple"
- local graph = require "volksdata.graph"
- local pkar = require "pocket_archive"
- local logger = pkar.logger
- local model = require "pocket_archive.model"
- local transformers = require "pocket_archive.transformers"
- local dbg = require "debugger"
- -- "nil" table - for missing key fallback in chaining.
- local NT = {}
- -- Default store graph to search all triples.
- local gr
- -- HTML templates. Compile them only once.
- -- TODO Add override for user-maintained templates.
- local fh, idx_tpl, dres_tpl, ores_tpl
- fh = datafile.open("templates/index.html")
- idx_tpl = assert(etlua.compile(fh:read("a")))
- fh:close()
- fh = datafile.open("templates/dres.html")
- dres_tpl = assert(etlua.compile(fh:read("a")))
- fh:close()
- fh = datafile.open("templates/ores.html")
- ores_tpl = assert(etlua.compile(fh:read("a")))
- fh:close()
- fh = datafile.open("templates/header.html")
- header_tpl = assert(etlua.compile(fh:read("a")))
- fh:close()
- -- HTML generator module.
- local M = {
- res_dir = plpath.join(pkar.config.htmlgen.out_dir, "res"),
- asset_dir = plpath.join(pkar.config.htmlgen.out_dir, "assets"),
- media_dir = plpath.join(pkar.config.htmlgen.out_dir, "media"),
- }
- local function get_breadcrumbs(mconf)
- -- Breadcrumbs, from top class to current class.
- -- Also verify if it's a File subclass.
- local breadcrumbs = {}
- for i = 1, #mconf.lineage do
- breadcrumbs[i] = {
- mconf.lineage[i],
- model.models[mconf.lineage[i]].label
- }
- end
- return breadcrumbs
- end
- local function get_tn_url(s)
- if gr:attr(s, pkar.RDF_TYPE)[pkar.FILE_T.hash] then
- -- The subject is a file.
- tn_fname = (s.data:gsub(pkar.PAR_NS, "") .. ".jpg") -- FIXME do not hardcode.
- return plpath.join(
- M.media_dir, tn_fname:sub(1, 2), tn_fname:sub(3, 4), tn_fname)
- end
- -- Recurse through all first children until one with a thumbnail, or a
- -- leaf without children, is found.
- local first_child
- _, first_child = next(gr:attr(s, pkar.FIRST_P))
- if first_child then return get_tn_url(first_child) end
- end
- local function generate_dres(s, mconf)
- local dmd = {}
- local rel = {}
- local children = {}
- local title
- -- Metadata
- local attrs = gr:connections(s, term.LINK_OUTBOUND)
- for p, ots in pairs(attrs) do
- local fname = nsm.denormalize_uri(p.data)
- p_label = ((mconf.properties or NT)[fname] or NT).label
- -- RDF types are shown in in breadcrumbs.
- if fname == "rdf:type" then goto skip
- elseif ((mconf.properties or NT)[fname] or NT).type == "rel" then
- -- Relationship.
- rel[fname] = {label = p_label, uri = fname}
- for _, o in pairs(ots) do table.insert(dmd[fname], o.data) end
- elseif fname == "pas:first" then
- -- Build a linked list for every first found.
- for _, o in pairs(ots) do
- -- Loop through all first children.
- local child_s = o
- logger:debug("local child_s: ", child_s.data)
- local ll = {}
- -- Fallback labels.
- local label
- _, label = next(gr:attr(child_s, pkar.DC_TITLE_P))
- if label then label = label.data
- else
- _, label = next(gr:attr(child_s, pkar.PATH_P))
- if label then label = plpath.basename(label.data)
- else label = child_s.data end
- end
- while child_s do
- -- Loop trough all next nodes for each first child.
- require "debugger".assert(get_tn_url(child_s))
- table.insert(ll, {
- href = pkar.gen_pairtree(
- "/res", child_s.data, ".html", true),
- label = label,
- tn = get_tn_url(child_s):gsub(M.media_dir, "/media/tn"),
- })
- logger:debug("Child label for ", child_s.data, ": ", ll[#ll].label or "nil")
- -- There can only be one "next"
- _, child_s = next(gr:attr(child_s, pkar.NEXT_P))
- end
- table.insert(children, ll)
- end
- elseif fname == "pas:next" then
- -- Sibling.
- for _, o in pairs(ots) do ls_next = o.data break end
- else
- -- Descriptive metadata.
- local attr = {label = p_label, uri = fname}
- -- TODO differentiate term types
- for _, o in pairs(ots) do table.insert(attr, o.data) end
- table.sort(attr)
- if p == pkar.DC_TITLE_P then title = attr[1] end
- table.insert(dmd, attr)
- end
- ::skip::
- end
- table.sort(
- dmd, function(a, b)
- return ((a.label or a.uri) < (b.label or b.uri))
- end
- )
- table.sort(rel)
- table.sort(children)
- logger:debug("Lineage:", pp.write(mconf.lineage))
- logger:debug("DMD:", pp.write(dmd))
- logger:debug("REL:", pp.write(rel))
- logger:debug("Children:", pp.write(children))
- logger:debug("Breadcrumbs:", pp.write(get_breadcrumbs(mconf)))
- out_html = dres_tpl({
- site_title = pkar.config.site.title or pkar.default_title,
- title = title or s.data,
- header_tpl = header_tpl,
- mconf = mconf,
- uri = s,
- dmd = dmd,
- rel = rel,
- children = children,
- ls_next = ls_next,
- breadcrumbs = get_breadcrumbs(mconf),
- rdf_href = pkar.gen_pairtree("/res", s.data, ".ttl", true),
- })
- local res_path = pkar.gen_pairtree(M.res_dir, s.data, ".html")
- local ofh = assert(io.open(res_path, "w"))
- ofh:write(out_html)
- ofh:close()
- return true
- end
- local function generate_ores(s, mconf)
- local techmd = {}
- local rel = {}
- -- Metadata
- local attrs = gr:connections(s, term.LINK_OUTBOUND)
- for p, ots in pairs(attrs) do
- local fname = nsm.denormalize_uri(p.data)
- p_label = ((mconf.properties or NT)[fname] or NT).label
- -- RDF types are shown in in breadcrumbs.
- if fname == "rdf:type" then goto skip
- elseif ((mconf.properties or NT)[fname] or NT).type == "rel" then
- -- Relationship.
- rel[fname] = {label = p_label, uri = fname}
- for _, o in pairs(ots) do table.insert(techmd[fname], o.data) end
- elseif fname == "pas:next" then
- -- Sibling.
- for _, o in pairs(ots) do ls_next = o.data break end
- else
- -- Descriptive metadata.
- techmd[fname] = {label = p_label, uri = fname}
- -- TODO differentiate term types
- for _, o in pairs(ots) do table.insert(techmd[fname], o.data) end
- table.sort(techmd[fname])
- end
- ::skip::
- end
- table.sort(techmd)
- table.sort(rel)
- logger:debug("Lineage:", pp.write(mconf.lineage))
- logger:debug("Breadcrumbs:", pp.write(get_breadcrumbs(mconf)))
- logger:debug("techmd:", pp.write(techmd))
- logger:debug("REL:", pp.write(rel))
- -- Transform and move media assets.
- local dest_fname, dest_dir, dest -- Reused for thumbnail.
- logger:info("Transforming resource file.")
- local res_path = techmd["pas:path"]
- if not res_path then error("No file path for File resource!") end
- local txconf = (mconf.transformers or NT).deliverable or {fn = "copy"}
- -- Set file name to resource ID + source extension.
- dest_fname = (
- s.data:gsub(pkar.PAR_NS, "") ..
- (txconf.ext or plpath.extension(res_path[1])))
- dest_dir = plpath.join(
- M.media_dir, dest_fname:sub(1, 2), dest_fname:sub(3, 4))
- dir.makepath(dest_dir)
- dest = plpath.join(dest_dir, dest_fname)
- assert(transformers[txconf.fn](
- res_path[1], dest, table.unpack(txconf or NT)))
- local deliverable = dest:gsub(pkar.config.htmlgen.out_dir, "")
- logger:info("Access file: ", deliverable)
- -- Thumbnail.
- local tn
- txconf = (mconf.transformers or NT).thumbnail
- if txconf then
- if txconf.ext then
- dest_fname = plpath.splitext(dest_fname) .. txconf.ext
- end
- dest_dir = plpath.join(
- M.media_dir, "tn", dest_fname:sub(1, 2), dest_fname:sub(3, 4))
- dir.makepath(dest_dir)
- dest = plpath.join(dest_dir, dest_fname)
- assert(transformers[txconf.fn](
- res_path[1], dest, table.unpack(txconf or NT)))
- tn = dest:gsub(M.media_dir, "/media/tn")
- logger:info("Thumbnail: ", tn)
- end
- out_html = ores_tpl({
- site_title = pkar.config.site.title or pkar.default_title,
- fname = plpath.basename(techmd["pas:sourcePath"][1]),
- header_tpl = header_tpl,
- mconf = mconf,
- uri = s,
- techmd = techmd,
- rel = rel,
- ls_next = ls_next,
- breadcrumbs = get_breadcrumbs(mconf),
- deliverable = deliverable,
- thumbnail = tn,
- rdf_href = pkar.gen_pairtree("/res", s.data, ".ttl", true),
- })
- local res_path = pkar.gen_pairtree(M.res_dir, s.data, ".html")
- local ofh = assert(io.open(res_path, "w"))
- ofh:write(out_html)
- ofh:close()
- return true
- end
- M.get_graph = function(s)
- out_gr = graph.new(nil, s.data)
- gr:copy(out_gr, s)
- return out_gr
- end
- M.generate_resource = function(s)
- local res_type
- _, res_type = next(gr:attr(s, pkar.CONTENT_TYPE_P))
- local mconf = model.models[res_type.data]
- -- Generate RDF/Turtle doc.
- local res_gr = M.get_graph(s)
- logger:debug("Serializing graph: ", s.data)
- local res_path = pkar.gen_pairtree(M.res_dir, s.data, ".ttl")
- local ofh = assert(io.open(res_path, "w"))
- ofh:write(res_gr:encode("ttl"))
- ofh:close()
- -- Generate HTML doc.
- if mconf.types["pas:File"] then return generate_ores(s, mconf)
- else return generate_dres(s, mconf) end
- end
- M.generate_resources = function()
- local subjects = gr:unique_terms(triple.POS_S)
- -- TODO parallelize
- for _, s in pairs(subjects) do assert(M.generate_resource(s)) end
- return true
- end
- M.generate_idx = function()
- local obj_idx = {}
- -- Get all subject of type: Artifact.
- s_ts = gr:term_set(
- pkar.RDF_TYPE, triple.POS_P,
- term.new_iriref_ns("pas:Artifact"), triple.POS_O
- )
- for _, s in pairs(s_ts) do
- local title, created
- _, title = next(gr:attr(s, pkar.DC_TITLE_P))
- _, created = next(gr:attr(s, pkar.DC_CREATED_P))
- local obj = {
- href = pkar.gen_pairtree("/res", s.data, ".html", true),
- title = title,
- created = created.data,
- tn = get_tn_url(s):gsub(M.media_dir, "/media/tn"),
- }
- table.insert(obj_idx, obj)
- end
- table.sort(obj_idx, function(a, b) return a.created < b.created end)
- logger:debug(pp.write(obj_idx))
- out_html = idx_tpl({
- title = pkar.config.site.title or pkar.default_title,
- header_tpl = header_tpl,
- nsm = nsm,
- obj_idx = obj_idx,
- })
- local idx_path = plpath.join(pkar.config.htmlgen.out_dir, "index.html")
- local ofh = assert(io.open(idx_path, "w"))
- logger:debug("Writing info at ", idx_path)
- ofh:write(out_html)
- ofh:close()
- return true
- end
- M.generate_site = function()
- if plpath.isdir(M.res_dir) then dir.rmtree(M.res_dir) end
- dir.makepath(M.res_dir)
- if plpath.isdir(M.asset_dir) then dir.rmtree(M.asset_dir) end
- dir.makepath(M.asset_dir)
- if plpath.isdir(M.media_dir) then dir.rmtree(M.media_dir) end
- dir.makepath(plpath.join(M.media_dir, "tn"))
- gr = graph.new(pkar.store, term.DEFAULT_CTX)
- assert(M.generate_resources())
- assert(M.generate_idx())
- dir.clonetree("templates/assets", plpath.dirname(M.asset_dir), dir.copyfile)
- end
- return M
|