html_generator.lua 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. local datafile = require "datafile"
  2. local dir = require "pl.dir"
  3. local etlua = require "etlua"
  4. local plpath = require "pl.path"
  5. local pp = require "pl.pretty"
  6. local nsm = require "lsup.namespace"
  7. local term = require "lsup.term"
  8. local triple = require "lsup.triple"
  9. local graph = require "lsup.graph"
  10. local pkar = require "pocket_archive"
  11. local logger = pkar.logger
  12. local model = require "pocket_archive.model"
  13. local transformers = require "pocket_archive.transformers"
  14. -- "nil" table - for missing key fallback in chaining.
  15. local NT = {}
  16. -- Compile all templates once.
  17. -- TODO Add override for user-maintained templates.
  18. local fh, idx_tpl, dres_tpl, ores_tpl
  19. fh = datafile.open("templates/index.html")
  20. idx_tpl = assert(etlua.compile(fh:read("a")))
  21. fh:close()
  22. fh = datafile.open("templates/dres.html")
  23. dres_tpl = assert(etlua.compile(fh:read("a")))
  24. fh:close()
  25. fh = datafile.open("templates/ores.html")
  26. ores_tpl = assert(etlua.compile(fh:read("a")))
  27. fh:close()
  28. -- HTML generator module.
  29. local M = {
  30. res_dir = pkar.config.htmlgen.out_dir .. "/res",
  31. asset_dir = pkar.config.htmlgen.out_dir .. "/assets",
  32. media_dir = pkar.config.htmlgen.out_dir .. "/media",
  33. }
  34. local function get_breadcrumbs(mconf)
  35. -- Breadcrumbs, from top class to current class.
  36. -- Also verify if it's a File subclass.
  37. local breadcrumbs = {}
  38. for i = 1, #mconf.lineage do
  39. breadcrumbs[i] = {
  40. mconf.lineage[i],
  41. model.models[mconf.lineage[i]].label
  42. }
  43. end
  44. return breadcrumbs
  45. end
  46. local function generate_dres(s, mconf)
  47. local gr = graph.new(pkar.store, term.DEFAULT_CTX)
  48. local dmd = {}
  49. local rel = {}
  50. local children = {}
  51. -- Metadata
  52. local attrs = gr:connections(s, term.LINK_OUTBOUND)
  53. --require "debugger"()
  54. for p, ots in pairs(attrs) do
  55. local fname = nsm.denormalize_uri(p.data)
  56. p_label = ((mconf.properties or NT)[fname] or NT).label
  57. -- RDF types are shown in in breadcrumbs.
  58. if fname == "rdf:type" then goto skip
  59. elseif ((mconf.properties or NT)[fname] or NT).type == "rel" then
  60. -- Relationship.
  61. rel[fname] = {label = p_label, uri = fname}
  62. for o in pairs(ots) do table.insert(dmd[fname], o.data) end
  63. elseif fname == "pas:first" then
  64. -- Build a linked list for every first found.
  65. local p = term.new_iriref_ns("pas:next")
  66. local dc_title = term.new_iriref_ns("dc:title")
  67. local tn_p = term.new_iriref_ns("pas:thumbnail")
  68. for o in pairs(ots) do
  69. -- Loop through all first children.
  70. local node_uri = o
  71. logger:debug("local node_uri", node_uri.data)
  72. local ll = {}
  73. while node_uri do
  74. -- Loop trough all next nodes for each first child.
  75. local el_gr = graph.get(node_uri, pkar.store)
  76. table.insert(ll, {
  77. uri = node_uri,
  78. label = gr:attr(node_uri, dc_title)[1],
  79. tn = gr:attr(node_uri, tn_p)[1],
  80. })
  81. local next_attr = gr:attr(node_uri, p)
  82. node_uri = next(next_attr) -- There can only be one "next"
  83. end
  84. table.insert(children, ll)
  85. end
  86. elseif fname == "pas:next" then
  87. -- Sibling.
  88. for o in pairs(ots) do ls_next = o.data break end
  89. else
  90. -- Descriptive metadata.
  91. dmd[fname] = {label = p_label, uri = fname}
  92. -- TODO differentiate term types
  93. for o in pairs(ots) do table.insert(dmd[fname], o.data) end
  94. table.sort(dmd[fname])
  95. end
  96. ::skip::
  97. end
  98. table.sort(dmd)
  99. table.sort(rel)
  100. table.sort(children)
  101. logger:debug("Lineage:", pp.write(mconf.lineage))
  102. logger:debug("DMD:", pp.write(dmd))
  103. logger:debug("REL:", pp.write(rel))
  104. logger:debug("Children:", pp.write(children))
  105. logger:debug("Breadcrumbs:", pp.write(get_breadcrumbs(mconf)))
  106. out_html = dres_tpl({
  107. site_title = pkar.config.site.title or pkar.default_title,
  108. title = ((dmd["dc:title"] or NT)[1] or NT) or s.data,
  109. mconf = mconf,
  110. uri = s,
  111. dmd = dmd,
  112. rel = rel,
  113. children = children,
  114. ls_next = ls_next,
  115. breadcrumbs = get_breadcrumbs(mconf),
  116. deliverable = deliverable,
  117. })
  118. local res_id = s.data:gsub(nsm.get_ns("par"), "")
  119. local ofh = assert(io.open(string.format(
  120. "%s/%s.html", M.res_dir, res_id), "w"))
  121. ofh:write(out_html)
  122. ofh:close()
  123. end
  124. local function generate_ores(s, mconf)
  125. local gr = graph.new(pkar.store, term.DEFAULT_CTX)
  126. local techmd = {}
  127. local rel = {}
  128. -- Metadata
  129. local attrs = gr:connections(s, term.LINK_OUTBOUND)
  130. for p, ots in pairs(attrs) do
  131. local fname = nsm.denormalize_uri(p.data)
  132. p_label = ((mconf.properties or NT)[fname] or NT).label
  133. -- RDF types are shown in in breadcrumbs.
  134. if fname == "rdf:type" then goto skip
  135. elseif ((mconf.properties or NT)[fname] or NT).type == "rel" then
  136. -- Relationship.
  137. rel[fname] = {label = p_label, uri = fname}
  138. for o in pairs(ots) do table.insert(techmd[fname], o.data) end
  139. elseif fname == "pas:next" then
  140. -- Sibling.
  141. for o in pairs(ots) do ls_next = o.data break end
  142. else
  143. -- Descriptive metadata.
  144. techmd[fname] = {label = p_label, uri = fname}
  145. -- TODO differentiate term types
  146. for o in pairs(ots) do table.insert(techmd[fname], o.data) end
  147. table.sort(techmd[fname])
  148. end
  149. ::skip::
  150. end
  151. table.sort(techmd)
  152. table.sort(rel)
  153. logger:debug("Lineage:", pp.write(mconf.lineage))
  154. logger:debug("Breadcrumbs:", pp.write(get_breadcrumbs(mconf)))
  155. logger:debug("techmd:", pp.write(techmd))
  156. logger:debug("REL:", pp.write(rel))
  157. --require "debugger"()
  158. -- Transform and move media assets.
  159. logger:info("Transforming resource file.")
  160. local res_path = techmd["pas:path"]
  161. if not res_path then error("No file path for File resource!") end
  162. local txconf = (mconf.transformers or NT).deliverable or {fn = "copy"}
  163. local dest_fname = plpath.basename(res_path[1])
  164. if txconf.ext then
  165. dest_fname = plpath.splitext(dest_fname) .. txconf.ext
  166. end
  167. dest = M.asset_dir .. "/" .. dest_fname
  168. assert(transformers[txconf.fn](
  169. res_path[1], dest, table.unpack(txconf or NT)))
  170. local deliverable = dest:gsub(pkar.config.htmlgen.out_dir, "..")
  171. logger:info("Deliverable: ", dest)
  172. out_html = ores_tpl({
  173. site_title = pkar.config.site.title or pkar.default_title,
  174. fname = plpath.basename(techmd["pas:sourcePath"][1]),
  175. mconf = mconf,
  176. uri = s,
  177. techmd = techmd,
  178. rel = rel,
  179. ls_next = ls_next,
  180. breadcrumbs = get_breadcrumbs(mconf),
  181. deliverable = deliverable,
  182. })
  183. local res_id = s.data:gsub(nsm.get_ns("par"), "")
  184. local ofh = assert(io.open(string.format(
  185. "%s/%s.html", M.res_dir, res_id), "w"))
  186. ofh:write(out_html)
  187. ofh:close()
  188. end
  189. M.generate_resource = function(s)
  190. local gr = graph.new(pkar.store, term.DEFAULT_CTX)
  191. local res_type
  192. local type_attr = gr:attr(
  193. s, term.new_iriref_ns("pas:contentType"))
  194. res_type = next(type_attr).data
  195. local mconf = model.models[res_type]
  196. if mconf.types["pas:File"] then return generate_ores(s, mconf)
  197. else return generate_dres(s, mconf) end
  198. end
  199. M.generate_resources = function()
  200. dir.rmtree(M.res_dir)
  201. dir.makepath(M.res_dir)
  202. dir.rmtree(M.asset_dir)
  203. dir.makepath(M.asset_dir)
  204. dir.rmtree(M.media_dir)
  205. dir.makepath(M.media_dir)
  206. local gr = graph.new(pkar.store, term.DEFAULT_CTX)
  207. local subjects = gr:unique_terms(triple.POS_S)
  208. -- TODO parallelize
  209. for s in pairs(subjects) do M.generate_resource(s) end
  210. end
  211. M.generate_idx = function()
  212. local obj_idx = {}
  213. local gr = graph.new(pkar.store, term.DEFAULT_CTX)
  214. -- Get all subject of type: Artifact.
  215. s_ts = gr:term_set(
  216. pkar.RDF_TYPE, triple.POS_P,
  217. term.new_iriref_ns("pas:Artifact"), triple.POS_O
  218. )
  219. for s in pairs(s_ts) do
  220. local s_label = nsm.denormalize_uri(s.data)
  221. local titles = gr:attr(s, term.new_iriref_ns("dc:title"))
  222. local obj = {title = next(titles)}
  223. if obj.title then obj_idx[s_label] = obj end
  224. end
  225. logger:debug(pp.write(obj_idx))
  226. out_html = idx_tpl({
  227. title = pkar.config.site.title or pkar.default_title,
  228. nsm = nsm,
  229. obj_idx = obj_idx,
  230. })
  231. local idx_path = pkar.config.htmlgen.out_dir .. "/index.html"
  232. local ofh = assert(io.open(idx_path, "w"))
  233. logger:debug("Writing info at ", idx_path)
  234. ofh:write(out_html)
  235. ofh:close()
  236. end
  237. return M