|
@@ -1,130 +1,272 @@
|
|
|
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, res_tpl, err
|
|
|
+local fh, idx_tpl, dres_tpl, ores_tpl
|
|
|
fh = datafile.open("templates/index.html")
|
|
|
-idx_tpl, err = etlua.compile(fh:read("a"))
|
|
|
-if not idx_tpl then error(err) end
|
|
|
+idx_tpl = assert(etlua.compile(fh:read("a")))
|
|
|
fh:close()
|
|
|
|
|
|
-fh = datafile.open("templates/res.html")
|
|
|
-res_tpl, err = etlua.compile(fh:read("a"))
|
|
|
-if not res_tpl then error(err) end
|
|
|
+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",
|
|
|
}
|
|
|
|
|
|
-M.generate_resource = function(gr_uri)
|
|
|
- local gr = graph.get(gr_uri, pkar.store)
|
|
|
+
|
|
|
+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 = {}
|
|
|
-
|
|
|
- local res_type
|
|
|
- local type_attr = gr:attr(
|
|
|
- gr_uri, term.new_iriref("pas:contentType", pkar.nsm))
|
|
|
- res_type = type_attr[1].data
|
|
|
- local mconf = model.models[res_type]
|
|
|
-
|
|
|
- local attrs = gr:connections(gr_uri, term.LINK_OUTBOUND)
|
|
|
+ -- Metadata
|
|
|
+ local attrs = gr:connections(s, term.LINK_OUTBOUND)
|
|
|
+ --require "debugger"()
|
|
|
for p, ots in pairs(attrs) do
|
|
|
- if ((mconf.attributes or {})[p.data] or {}).type == "rel" then
|
|
|
+ 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[p.data] = {}
|
|
|
- for o in pairs(ots) do table.insert(dmd[p.data], o.data) end
|
|
|
- elseif p.data == "pas:firstChild" then
|
|
|
- -- Children.
|
|
|
- for o in pairs(ots) do table.insert(children, o.data) end
|
|
|
- elseif p.data == "pas:next" then
|
|
|
+ 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[p.data] = {}
|
|
|
+ dmd[fname] = {label = p_label, uri = fname}
|
|
|
-- TODO differentiate term types
|
|
|
- for o in pairs(ots) do table.insert(dmd[p.data], o.data) end
|
|
|
+ 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)
|
|
|
- print("DMD:")
|
|
|
- pp.dump(dmd)
|
|
|
- print("REL:")
|
|
|
- pp.dump(rel)
|
|
|
-
|
|
|
- out_html = res_tpl({
|
|
|
- title = pkar.config.site.title or "Pocket Archive",
|
|
|
- uri = gr_uri,
|
|
|
- pkar = pkar,
|
|
|
+ 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 = gr_uri.data:gsub(pkar.nsm:get_ns("par"), "")
|
|
|
+ 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"))
|
|
|
- return ofh:write(out_html)
|
|
|
+ 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()
|
|
|
- -- TODO parallelize
|
|
|
+ dir.rmtree(M.res_dir)
|
|
|
dir.makepath(M.res_dir)
|
|
|
- local index_ts = graph.list(pkar.store)
|
|
|
- for gr_uri in pairs(index_ts) do M.generate_resource(gr_uri) end
|
|
|
+ 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 index_ts = graph.list(pkar.store)
|
|
|
- for gr_uri in pairs(index_ts) do
|
|
|
- local obj = {}
|
|
|
- local cur
|
|
|
- gr = graph.get(gr_uri, pkar.store)
|
|
|
- -- Get all subject of type: Artifact.
|
|
|
- s_ts = gr:term_set(
|
|
|
- term.new_iriref("rdf:type", pkar.nsm), triple.POS_P,
|
|
|
- term.new_iriref("pas:Artifact", pkar.nsm), triple.POS_O
|
|
|
- )
|
|
|
- for s in pairs(s_ts) do
|
|
|
- for trp in gr:lookup(s, term.new_iriref("dc:title", pkar.nsm)) do
|
|
|
- obj.title = trp.o
|
|
|
- break -- Only one value to use
|
|
|
- end
|
|
|
- end
|
|
|
- if obj.title then obj_idx[gr_uri.data] = obj end
|
|
|
+ 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
|
|
|
|
|
|
- pp.dump(obj_idx)
|
|
|
+ logger:debug(pp.write(obj_idx))
|
|
|
out_html = idx_tpl({
|
|
|
- title = pkar.config.site.title or "Pocket Archive",
|
|
|
- pkar = pkar,
|
|
|
+ title = pkar.config.site.title or pkar.default_title,
|
|
|
+ nsm = nsm,
|
|
|
obj_idx = obj_idx,
|
|
|
})
|
|
|
|
|
|
- local ofh = assert(io.open(
|
|
|
- pkar.config.htmlgen.out_dir .. "/index.html", "w"))
|
|
|
+ local idx_path = pkar.config.htmlgen.out_dir .. "/index.html"
|
|
|
+ local ofh = assert(io.open(idx_path, "w"))
|
|
|
|
|
|
- return ofh:write(out_html)
|
|
|
+ logger:debug("Writing info at ", idx_path)
|
|
|
+ ofh:write(out_html)
|
|
|
+ ofh:close()
|
|
|
end
|
|
|
|
|
|
|