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 "lsup.namespace" local term = require "lsup.term" local triple = require "lsup.triple" local graph = require "lsup.graph" local pkar = require "pocket_archive" local logger = pkar.logger local model = require "pocket_archive.model" local transformers = require "pocket_archive.transformers" -- "nil" table - for missing key fallback in chaining. local NT = {} -- Compile all templates 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() -- HTML generator module. local M = { res_dir = pkar.config.htmlgen.out_dir .. "/res", asset_dir = pkar.config.htmlgen.out_dir .. "/assets", media_dir = 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 generate_dres(s, mconf) local gr = graph.new(pkar.store, term.DEFAULT_CTX) local dmd = {} local rel = {} local children = {} -- Metadata local attrs = gr:connections(s, term.LINK_OUTBOUND) --require "debugger"() 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. local p = term.new_iriref_ns("pas:next") local dc_title = term.new_iriref_ns("dc:title") local tn_p = term.new_iriref_ns("pas:thumbnail") for o in pairs(ots) do -- Loop through all first children. local node_uri = o logger:debug("local node_uri", node_uri.data) local ll = {} while node_uri do -- Loop trough all next nodes for each first child. local el_gr = graph.get(node_uri, pkar.store) table.insert(ll, { uri = node_uri, label = gr:attr(node_uri, dc_title)[1], tn = gr:attr(node_uri, tn_p)[1], }) local next_attr = gr:attr(node_uri, p) node_uri = next(next_attr) -- There can only be one "next" 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. dmd[fname] = {label = p_label, uri = fname} -- TODO differentiate term types for o in pairs(ots) do table.insert(dmd[fname], o.data) end table.sort(dmd[fname]) end ::skip:: end table.sort(dmd) 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 = ((dmd["dc:title"] or NT)[1] or NT) or s.data, mconf = mconf, uri = s, dmd = dmd, rel = rel, children = children, ls_next = ls_next, breadcrumbs = get_breadcrumbs(mconf), deliverable = deliverable, }) local res_id = s.data:gsub(nsm.get_ns("par"), "") local ofh = assert(io.open(string.format( "%s/%s.html", M.res_dir, res_id), "w")) ofh:write(out_html) ofh:close() end local function generate_ores(s, mconf) local gr = graph.new(pkar.store, term.DEFAULT_CTX) 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)) --require "debugger"() -- Transform and move media assets. 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"} local dest_fname = plpath.basename(res_path[1]) if txconf.ext then dest_fname = plpath.splitext(dest_fname) .. txconf.ext end dest = M.asset_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("Deliverable: ", dest) out_html = ores_tpl({ site_title = pkar.config.site.title or pkar.default_title, fname = plpath.basename(techmd["pas:sourcePath"][1]), mconf = mconf, uri = s, techmd = techmd, rel = rel, ls_next = ls_next, breadcrumbs = get_breadcrumbs(mconf), deliverable = deliverable, }) local res_id = s.data:gsub(nsm.get_ns("par"), "") local ofh = assert(io.open(string.format( "%s/%s.html", M.res_dir, res_id), "w")) ofh:write(out_html) ofh:close() end M.generate_resource = function(s) local gr = graph.new(pkar.store, term.DEFAULT_CTX) local res_type local type_attr = gr:attr( s, term.new_iriref_ns("pas:contentType")) res_type = next(type_attr).data local mconf = model.models[res_type] if mconf.types["pas:File"] then return generate_ores(s, mconf) else return generate_dres(s, mconf) end end M.generate_resources = function() dir.rmtree(M.res_dir) dir.makepath(M.res_dir) dir.rmtree(M.asset_dir) dir.makepath(M.asset_dir) dir.rmtree(M.media_dir) dir.makepath(M.media_dir) local gr = graph.new(pkar.store, term.DEFAULT_CTX) local subjects = gr:unique_terms(triple.POS_S) -- TODO parallelize for s in pairs(subjects) do M.generate_resource(s) end end M.generate_idx = function() local obj_idx = {} local gr = graph.new(pkar.store, term.DEFAULT_CTX) -- 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 s_label = nsm.denormalize_uri(s.data) local titles = gr:attr(s, term.new_iriref_ns("dc:title")) local obj = {title = next(titles)} if obj.title then obj_idx[s_label] = obj end end logger:debug(pp.write(obj_idx)) out_html = idx_tpl({ title = pkar.config.site.title or pkar.default_title, nsm = nsm, obj_idx = obj_idx, }) local idx_path = 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() end return M