Browse Source

Functional, bare-bones prototype:

* Rewrite model typedefs in Lua
* Add more typedefs
* Model cache
* Very crude but functional HTML generator
scossu 3 weeks ago
parent
commit
b4dd5084af

+ 1 - 0
.gitignore

@@ -43,4 +43,5 @@ luac.out
 vgcore*
 vgcore*
 data/ores/*
 data/ores/*
 data/dres/*
 data/dres/*
+out/*
 !.keep
 !.keep

+ 8 - 1
config/app.lua

@@ -14,7 +14,7 @@ return {
         -- Single-valued fields. TODO rely on content model cardinality.
         -- Single-valued fields. TODO rely on content model cardinality.
         single_values = {
         single_values = {
             ["dc:identifier"] = true,
             ["dc:identifier"] = true,
-            ["dc:type"] = true,
+            ["pas:contentType"] = true,
             ["dc:title"] = true,
             ["dc:title"] = true,
             ["path"] = true,
             ["path"] = true,
             ["pas:sourcePath"] = true,
             ["pas:sourcePath"] = true,
@@ -42,6 +42,13 @@ return {
         rdfs = "http://www.w3.org/2000/01/rdf-schema#",
         rdfs = "http://www.w3.org/2000/01/rdf-schema#",
         xsd = "http://www.w3.org/2001/XMLSchema#",
         xsd = "http://www.w3.org/2001/XMLSchema#",
     },
     },
+
+    -- Static and dynamic site settings.
+    site = {
+        title = "Pocket Archive demo site"
+    },
+
+    -- Static site generation settings.
     htmlgen = {
     htmlgen = {
         out_dir = "./out/html",
         out_dir = "./out/html",
     },
     },

+ 4 - 0
config/model/typedef/agent.lua

@@ -0,0 +1,4 @@
+return {
+  label = "Agent",
+  broader = "pas:Anything"
+}

+ 0 - 3
config/model/typedef/agent.yml

@@ -1,3 +0,0 @@
----
-label: Agent
-broader: "pas:Anything"

+ 50 - 0
config/model/typedef/anything.lua

@@ -0,0 +1,50 @@
+return {
+    id = "pas:Anything",
+    label = "Anything",
+    description = "Superclass of every resource type in the system.",
+    abstract = "true",
+
+    properties = {
+        ["pas:id"] = {
+            label = "Primary ID",
+            type = "string",
+            min_cardinality = 1,
+            max_cardinality = 1,
+        },
+        ["pas:refId"] = {
+            label = "External system ID",
+            type = "string",
+        },
+        ["pas:prefLabel"] = {
+            label = "Preferred Label",
+            type = "string",
+            max_cardinality = 1,
+        },
+        ["pas:altLabel"] = {
+            label = "Alternative Label",
+            type = "string",
+        },
+        ["pas:created"] = {
+            label = "Created On",
+            type = "timestamp",
+            min_cardinality = 1,
+            max_cardinality = 1,
+        },
+        ["pas:updated"] = {
+            label = "Last Updated On",
+            type = "timestamp",
+            min_cardinality = 1,
+            max_cardinality = 1,
+        },
+        ["pas:createdBy"] = {
+            label = "Created By",
+            type = "rel",
+            range = {["pas:Agent"] = true},
+        },
+        ["pas:updatedBy"] = {
+            label = "Last Updated By",
+            type = "rel",
+            range = {["pas:Agent"] = true},
+        },
+    },
+}

+ 0 - 46
config/model/typedef/anything.yml

@@ -1,46 +0,0 @@
----
-label: Anything
-description: Superclass of every resource type in the system.
-abstract: true
-
-properties:
-  "pas:id":
-    label: Primary ID
-    type: string
-    min_cardinality: 1
-    max_cardinality: 1
-
-  "pas:refId":
-    label: External system ID
-    type: string
-
-  "pas:prefLabel":
-    label: Preferred Label
-    type: string
-    max_cardinality: 1
-
-  "pas:altLabel":
-    label: Alternative Label
-    type: string
-
-  "pas:created":
-    label: Created On
-    type: timestamp
-    min_cardinality: 1
-    max_cardinality: 1
-
-  "pas:updated":
-    label: Last Updated On
-    type: timestamp
-    min_cardinality: 1
-    max_cardinality: 1
-
-  "pas:createdBy":
-    label: Created By
-    type: rel
-    range: ["pas:Agent"]
-
-  "pas:updatedBy":
-    label: Last Updated By
-    type: rel
-    range: ["pas:Agent"]

+ 14 - 0
config/model/typedef/artifact.lua

@@ -0,0 +1,14 @@
+return {
+    id = "pas:Artifact",
+    label = "Artifact",
+    description = "Intellectual work.",
+    broader = "pas:Anything",
+
+    properties = {
+        ["pas:hasFile"] = {
+            label = "Has file",
+            type = "rel",
+            range = {["pas:File"] = true},
+        },
+    },
+}

+ 0 - 10
config/model/typedef/artifact.yml

@@ -1,10 +0,0 @@
----
-label: Artifact
-description: Intellectual work.
-broader: "pas:Anything"
-
-properties:
-  "pas:hasFile":
-    label: Has file
-    type: rel
-    range: ["pas:File"]

+ 6 - 0
config/model/typedef/document.lua

@@ -0,0 +1,6 @@
+return {
+    id = "pas:Document",
+    label = "Document",
+    broader = "pas:Artifact",
+}
+

+ 13 - 0
config/model/typedef/file.lua

@@ -0,0 +1,13 @@
+return {
+    label = "File",
+    broader = "pas:Anything",
+
+    properties = {
+        ["pas:location"] = {
+            label = "Location",
+            type = "string",
+            min_cardinality = 1,
+            max_cardinality = 1,
+        }
+    }
+}

+ 0 - 10
config/model/typedef/file.yml

@@ -1,10 +0,0 @@
----
-label: File
-broader: "pas:Anything"
-
-properties:
-  "pas:location":
-    label: Location
-    type: string
-    min_cardinality: 1
-    max_cardinality: 1

+ 36 - 0
config/model/typedef/part.lua

@@ -0,0 +1,36 @@
+--[[
+Part content type = "a logical subdivision within an artifact.",
+Resources of this type may be used to build logical structures implemented
+as linked lists.
+
+The `pas = "first` property points to the first child in a",
+linked list nested inside the current resource. There may be any number of
+linked list heads under the same Part, or none.
+
+The `pas = "next` property points to the next sibling in a linked list. The",
+last item in a list is identified by the lack of this property.
+
+In a more complex hierarchy, any given Part may have both "first" and
+"next" properties.
+--]]
+
+return {
+    id = "pas:Part",
+    label = "Part",
+
+    broader = "pas:Anything",
+
+    properties = {
+        ["pas:first"] = {
+            label = "First child",
+            type = "resource",
+            range = {["pas:Part"] = true},
+        },
+        ["pas:next"] = {
+            label = "Next sibling",
+            type = "resource",
+            range = {["pas:Part"] = true},
+            max_cardinality = "1",
+        }
+    }
+}

+ 0 - 33
config/model/typedef/part.yml

@@ -1,33 +0,0 @@
-#
-# Part content type: a logical subdivision within an artifact.
-# Resources of this type may be used to build logical structures implemented
-# as linked lists.
-#
-# The `pas:first` property points to the first child in a
-# linked list nested inside the current resource. There may be any number of
-# linked list heads under the same Part, or none.
-#
-# The `pas:next` property points to the next sibling in a linked list. The
-# last item in a list is identified by the lack of this property.
-#
-# In a more complex hierarchy, any given Part may have both "first" and
-# "next" properties.
-
----
-id: "pas:Part"
-ls_code: Part
-label: Part
-
-broader: "pas:Anything"
-
-properties:
-  "pas:first":
-    label: First child
-    type: resource
-    range: ["pas:Part"]
-
-  "pas:next":
-    label: Next sibling
-    type: resource
-    range: ["pas:Part"]
-    max_cardinality: 1

+ 17 - 0
config/model/typedef/postcard.lua

@@ -0,0 +1,17 @@
+return {
+    id = "pas:Postcard",
+    label = "Postcard",
+    broader = "pas:Document",
+    properties = {
+        ["pas:recto"] = {
+            label = "Recto",
+            type = "rel",
+            range = {["pas:Part"] = true},
+        },
+        ["pas:verso"] = {
+            label = "Verso",
+            type = "rel",
+            range = {["pas:Part"] = true},
+        },
+    }
+}

+ 5 - 0
config/model/typedef/still_image.lua

@@ -0,0 +1,5 @@
+return {
+    id = "pas:StillImage",
+    label = "Still Image",
+    broader = "pas:Artifact",
+}

+ 5 - 0
config/model/typedef/still_image_file.lua

@@ -0,0 +1,5 @@
+return {
+    id = "dctype:StillImage",
+    label = "Still Image File",
+    broader = "pas:File",
+}

+ 1 - 1
pocket_archive-scm-1.rockspec

@@ -30,7 +30,7 @@ build = {
     type = "builtin",
     type = "builtin",
     modules = {
     modules = {
         ["pocket_archive"] = "src/core.lua",
         ["pocket_archive"] = "src/core.lua",
-        ["pocket_archive.model_parser"] = "src/model_parser.lua",
+        ["pocket_archive.model"] = "src/model.lua",
         ["pocket_archive.submission"] = "src/submission.lua",
         ["pocket_archive.submission"] = "src/submission.lua",
         ["pocket_archive.html_generator"] = "src/html_generator.lua",
         ["pocket_archive.html_generator"] = "src/html_generator.lua",
         ["pocket_archive.monocypher"] = {
         ["pocket_archive.monocypher"] = {

+ 1 - 1
scratch.lua

@@ -20,4 +20,4 @@ idx = graph.list(pkar.store)
 --assert(#idx == 5)
 --assert(#idx == 5)
 
 
 html = hgen.generate_idx()
 html = hgen.generate_idx()
-pp.dump(html)
+res_html = hgen.generate_resources()

+ 74 - 7
src/html_generator.lua

@@ -1,32 +1,99 @@
 local datafile = require "datafile"
 local datafile = require "datafile"
+local dir = require "pl.dir"
 local etlua = require "etlua"
 local etlua = require "etlua"
 local pp = require "pl.pretty"
 local pp = require "pl.pretty"
 
 
 local term = require "lsup.term"
 local term = require "lsup.term"
+local triple = require "lsup.triple"
 local graph = require "lsup.graph"
 local graph = require "lsup.graph"
 
 
 local pkar = require "pocket_archive"
 local pkar = require "pocket_archive"
 
 
 
 
-local M = {}
+-- Compile all templates once.
+-- TODO Add override for user-maintained templates.
+local fh, idx_tpl, res_tpl, err
+fh = datafile.open("templates/index.html")
+idx_tpl, err = etlua.compile(fh:read("a"))
+if not idx_tpl then error(err) end
+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:close()
+
+
+local M = {
+    res_dir = pkar.config.htmlgen.out_dir .. "/res",
+}
+
+M.generate_resource = function(gr_uri)
+    local gr = graph.get(gr_uri, pkar.store)
+    local dmd = {}
+    local rel = {}
+
+    for trp in gr:lookup(gr_uri) do
+        -- TODO transform data here according to model.
+        dmd[trp.p.data] = trp.o
+    end
+    pp.dump(rsrc_data)
+
+    out_html = res_tpl({
+        title = pkar.config.site.title or "Pocket Archive",
+        uri = gr_uri,
+        pkar = pkar,
+        dmd = dmd,
+        rel = rel,
+    })
+
+    local res_id = gr_uri.data:gsub(pkar.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)
+end
+
+
+M.generate_resources = function()
+    -- TODO parallelize
+    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
+end
+
 
 
 M.generate_idx = function()
 M.generate_idx = function()
     local obj_idx = {}
     local obj_idx = {}
     local index_ts = graph.list(pkar.store)
     local index_ts = graph.list(pkar.store)
     for gr_uri in pairs(index_ts) do
     for gr_uri in pairs(index_ts) do
         local obj = {}
         local obj = {}
+        local cur
         gr = graph.get(gr_uri, pkar.store)
         gr = graph.get(gr_uri, pkar.store)
-        for trp in gr:lookup(gr_uri, term.new_iriref("dc:title")) do
-            if trp.o then obj.title = trp.o end
+        -- 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
         end
         if obj.title then obj_idx[gr_uri.data] = obj end
         if obj.title then obj_idx[gr_uri.data] = obj end
     end
     end
 
 
-    local fh = datafile.open("templates/index.html")
-    local idx_tpl, err = etlua.compile(fh:read("a"))
-    if not idx then error(err) end
     pp.dump(obj_idx)
     pp.dump(obj_idx)
-    print(idx_tpl({obj_idx = obj_idx, term = term}))
+    out_html = idx_tpl({
+        title = pkar.config.site.title or "Pocket Archive",
+        pkar = pkar,
+        obj_idx = obj_idx,
+    })
+
+    local ofh = assert(io.open(
+            pkar.config.htmlgen.out_dir .. "/index.html", "w"))
+
+    return ofh:write(out_html)
 end
 end
 
 
 
 

+ 16 - 10
src/model_parser.lua → src/model.lua

@@ -2,10 +2,8 @@ local string = string
 local table = table
 local table = table
 local io = io
 local io = io
 
 
-local lyaml = require "lyaml"
 
 
-
-local M = {}
+local M = {models = {}}
 
 
 -- Parameters that do not get inherited.
 -- Parameters that do not get inherited.
 local NO_INHERIT = {abstract = true}
 local NO_INHERIT = {abstract = true}
@@ -13,9 +11,10 @@ local MODEL_PATH = "./config/model/typedef/"
 
 
 
 
 local function camel2snake(src)
 local function camel2snake(src)
-    return src:lower()
+    return src
         :gsub("^pas:", "")  -- Strip namespace.
         :gsub("^pas:", "")  -- Strip namespace.
         :gsub("([^^])(%u)", "%1_%2")  -- Uppercase (except initial) to _.
         :gsub("([^^])(%u)", "%1_%2")  -- Uppercase (except initial) to _.
+        :lower()
 end
 end
 
 
 
 
@@ -25,11 +24,8 @@ M.parse_model = function(mod_id)
     local function traverse(mod_id)
     local function traverse(mod_id)
         print("traversing:", mod_id)
         print("traversing:", mod_id)
         local fname = camel2snake (mod_id)
         local fname = camel2snake (mod_id)
-        local fh = assert(io.open(MODEL_PATH .. fname .. ".yml"), "r")
-        local yml_data = fh:read("a")
-        fh:close()
-        local model = lyaml.load(yml_data)
-        model.id = mod_id
+        local model = dofile(MODEL_PATH .. fname .. ".lua")
+        --model.id = mod_id
         --print("Model: ")
         --print("Model: ")
         --for k, v in pairs(model) do print (k, v) end
         --for k, v in pairs(model) do print (k, v) end
 
 
@@ -61,7 +57,7 @@ M.parse_model = function(mod_id)
             ::continue::
             ::continue::
         end
         end
     end
     end
-    local config = {}
+    local config = {lineage = lineage}
 
 
     for _, src_config in ipairs(hierarchy) do
     for _, src_config in ipairs(hierarchy) do
         merge(src_config, config)
         merge(src_config, config)
@@ -70,4 +66,14 @@ M.parse_model = function(mod_id)
     return config
     return config
 end
 end
 
 
+
+local models_mt = {
+    ["__index"] = function (t, model)
+        t[model] = M.parse_model(model)
+        return t[model]
+    end
+}
+setmetatable(M.models, models_mt)
+
+
 return M
 return M

+ 12 - 4
src/submission.lua

@@ -3,12 +3,14 @@ local io = io
 local csv = require "csv"
 local csv = require "csv"
 local dir = require "pl.dir"
 local dir = require "pl.dir"
 local plpath = require "pl.path"
 local plpath = require "pl.path"
+local pp = require "pl.pretty"
 
 
 local term = require "lsup.term"
 local term = require "lsup.term"
 local triple = require "lsup.triple"
 local triple = require "lsup.triple"
 local graph = require "lsup.graph"
 local graph = require "lsup.graph"
 
 
 local mc = require "pocket_archive.monocypher"
 local mc = require "pocket_archive.monocypher"
+local model = require "pocket_archive.model"
 local pkar = require "pocket_archive"
 local pkar = require "pocket_archive"
 
 
 
 
@@ -233,6 +235,9 @@ end
 
 
 M.update_rsrc_md = function(rsrc)
 M.update_rsrc_md = function(rsrc)
     -- TODO use a transaction when lsup_lua supports it.
     -- TODO use a transaction when lsup_lua supports it.
+    print("Updating resource md: ")
+    pp.dump(rsrc)
+    rmod = model.parse_model(rsrc["pas:contentType"])
     triples = {}
     triples = {}
 
 
     local s = term.new_iriref("par:" .. rsrc.id, pkar.nsm)
     local s = term.new_iriref("par:" .. rsrc.id, pkar.nsm)
@@ -247,6 +252,13 @@ M.update_rsrc_md = function(rsrc)
             end
             end
         else table.insert(triples, triple.new(s, p, term.new_lit(v))) end
         else table.insert(triples, triple.new(s, p, term.new_lit(v))) end
     end
     end
+    for i, m in ipairs(rmod.lineage) do
+        table.insert(triples, triple.new(
+            s, term.new_iriref("rdf:type", pkar.nsm),
+            term.new_iriref(m)
+        ))
+    end
+
     -- This is a full replacement.
     -- This is a full replacement.
     print("Removing triples.")
     print("Removing triples.")
     gr:remove();
     gr:remove();
@@ -257,10 +269,6 @@ M.update_rsrc_md = function(rsrc)
 end
 end
 
 
 
 
-M.update_md = function(sip)
-end
-
-
 M.deposit = function(sip)
 M.deposit = function(sip)
     for i, rsrc in ipairs(sip) do
     for i, rsrc in ipairs(sip) do
         -- TODO Wrap this chunk into a txn. Each row is atomic.
         -- TODO Wrap this chunk into a txn. Each row is atomic.

+ 6 - 3
templates/index.html

@@ -1,16 +1,19 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html>
 <html>
     <head>
     <head>
-        <title>Pocket Archive</title>
+        <title><%= title %></title>
     </head>
     </head>
     <body>
     <body>
-        <header></header>
+        <header>
+            <h1><%= title %></h1>
+        </header>
         <main>
         <main>
             <section id="obj_list">
             <section id="obj_list">
+                <h2>Recent artifacts</h2>
                 <ul>
                 <ul>
                 <% for uri, data in pairs(obj_idx) do %>
                 <% for uri, data in pairs(obj_idx) do %>
                     <li class="obj_link">
                     <li class="obj_link">
-                        <a href="<%= uri %>">
+                        <a href="<%= uri:gsub(pkar.nsm:get_ns("par"), "res/") %>.html">
                             <%= data.title.data %>
                             <%= data.title.data %>
                             <%if data.title.lang then %>
                             <%if data.title.lang then %>
                                 <span class="langtag"><%= data.title.lang %></span>
                                 <span class="langtag"><%= data.title.lang %></span>

+ 24 - 0
templates/res.html

@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title><%= title %>&emsp;:&emsp;<%= data["dc:title"].data %></title>
+    </head>
+    <body>
+        <header>
+            <h1><%= title %>&emsp;❁&emsp;<%= data["dc:title"].data %></h1>
+        </header>
+        <main>
+            <section id="res_md">
+                <h2>Metadata</h2>
+                <dl class="res_md">
+                <% for p, o in pairs(dmd) do %>
+                <dt><%= p %></dt>
+                <dd><%= o.data %></dd>
+                <% end %>
+                </dl>
+            </section>
+        </main>
+        <footer></footer>
+    </body>
+</html>
+

+ 2 - 2
test/sample_submission/postcard-bag/data/submission-v2.csv

@@ -1,4 +1,4 @@
-"path","dc:identifier","dc:type","dc:title","dc:alternative","dc:description"
+"path","dc:identifier","pas:contentType","dc:title","dc:alternative","dc:description"
 0001,0001,"Postcard","Example Postcard","This is an alternative label","Note that recto and verso representations have been named front and back, to emphasize that the ordering is not alphabetical."
 0001,0001,"Postcard","Example Postcard","This is an alternative label","Note that recto and verso representations have been named front and back, to emphasize that the ordering is not alphabetical."
 ,,,,"And this is another alternative label","Second description."
 ,,,,"And this is another alternative label","Second description."
 ,,,,"Yet another alt label.",
 ,,,,"Yet another alt label.",
@@ -7,4 +7,4 @@
 "0001/0001-back",0004,"Part","Verso",,
 "0001/0001-back",0004,"Part","Verso",,
 "0001/0001-back/567890.jpg",0005,"StillImageFile",,,
 "0001/0001-back/567890.jpg",0005,"StillImageFile",,,
 0006,0006,"StillImage","Single Image",,"Preparing kebab at Aqil's during curfew."
 0006,0006,"StillImage","Single Image",,"Preparing kebab at Aqil's during curfew."
-0006/0685_04.jpg,0007,"StillImageFile","Nablus 2002",,
+0006/0685_04.jpg,0007,"StillImageFile","B/W scan of physical photo",,