348 lines
8.4 KiB
Markdown
348 lines
8.4 KiB
Markdown
#meta
|
|
|
|
This implements library management
|
|
|
|
# Implementation
|
|
```space-lua
|
|
-- namespace
|
|
library = {
|
|
libraryTag = "meta/library",
|
|
remoteLibraryTag = "meta/library/remote",
|
|
repositoryTag = "meta/repository"
|
|
}
|
|
|
|
-- Harcoded prefixes
|
|
local libraryPrefix = "Library/"
|
|
local repositoryPrefix = "Repositories/"
|
|
|
|
-- Schema of frontmatter of the library page
|
|
-- Schema of #meta/library objects
|
|
local librarySchema = {
|
|
type = "object",
|
|
properties = {
|
|
files = schema.array(schema.string())
|
|
}
|
|
}
|
|
|
|
local remoteLibrarySchema = {
|
|
type = "object",
|
|
properties = {
|
|
name = schema.string(),
|
|
uri = schema.string(),
|
|
}
|
|
}
|
|
|
|
-- Repo management commands
|
|
command.define {
|
|
name = "Library: Add Repository",
|
|
run = function()
|
|
local repoUri = editor.prompt("URI:")
|
|
if not repoUri then
|
|
return
|
|
end
|
|
local text = net.readURI(repoUri, {encoding="text/markdown"})
|
|
local repoName = editor.prompt("Name:")
|
|
if not repoName then
|
|
return
|
|
end
|
|
local repoPage = repositoryPrefix .. repoName
|
|
if space.pageExists(repoPage) then
|
|
editor.flashNotification(repoPage .. " already exists", "error")
|
|
return
|
|
end
|
|
space.writePage(repoPage, share.setFrontmatter({
|
|
uri = repoUri,
|
|
hash = share.contentHash(text),
|
|
mode = "pull"
|
|
}, text))
|
|
editor.flashNotification "Library installed"
|
|
editor.navigate(repoPage)
|
|
reloadEverything()
|
|
end
|
|
}
|
|
|
|
command.define {
|
|
name = "Library: Update All Repositories",
|
|
run = function()
|
|
for repo in query[[from index.tag(library.repositoryTag)]] do
|
|
if repo.share then
|
|
share.sharePage(repo.name)
|
|
end
|
|
end
|
|
editor.flashNotification "All repositories updated"
|
|
end
|
|
}
|
|
|
|
-- Library management commands
|
|
command.define {
|
|
name = "Library: Install",
|
|
run = function()
|
|
local uri = editor.prompt("URI:")
|
|
if not uri then
|
|
return
|
|
end
|
|
local name = editor.prompt("Name (library will be saved under Library/<<Name>>):")
|
|
if not name then
|
|
return
|
|
end
|
|
local libPage = libraryPrefix .. name
|
|
if space.pageExists(libPage) then
|
|
editor.flashNotification(libPage .. " already exists", "error")
|
|
return
|
|
end
|
|
library.install(libPage, uri)
|
|
editor.flashNotification "Library installed"
|
|
reloadEverything()
|
|
editor.navigate("Library/Std/Pages/Library Manager")
|
|
end
|
|
}
|
|
|
|
command.define {
|
|
name = "Library: Update All",
|
|
run = function()
|
|
local installedLibraries = query[[from index.tag(library.libraryTag)]]
|
|
for lib in installedLibraries do
|
|
if library.update(lib.name, false) then
|
|
editor.flashNotification("Updated " .. lib.name)
|
|
end
|
|
end
|
|
editor.flashNotification "Update complete!"
|
|
reloadEverything()
|
|
end
|
|
}
|
|
|
|
command.define {
|
|
name = "Library: Manager",
|
|
run = function()
|
|
editor.navigate("Library/Std/Pages/Library Manager")
|
|
end
|
|
}
|
|
|
|
-- Look up an installed library by name
|
|
function library.getInstalled(uri)
|
|
local installedFMs = query[[
|
|
from index.tag(library.libraryTag)
|
|
where _.share and _.share.uri == uri
|
|
]]
|
|
if #installedFMs == 0 then
|
|
return nil
|
|
else
|
|
return installedFMs[1]
|
|
end
|
|
end
|
|
|
|
-- Install a library
|
|
function library.install(pageName, uri, currentHash)
|
|
-- Fetch from remote URL
|
|
local text = net.readURI(uri, {
|
|
encoding = "text/markdown"
|
|
})
|
|
|
|
if not text then
|
|
error("Could not fetch: " .. uri)
|
|
end
|
|
|
|
local remoteLibFM = index.extractFrontmatter(text).frontmatter
|
|
local remoteHash = share.contentHash(text)
|
|
|
|
local err = jsonschema.validateObject(librarySchema, remoteLibFM)
|
|
if err then
|
|
editor.flashNotification("Library frontmatter validation error: " .. err, "error")
|
|
return
|
|
end
|
|
|
|
-- Check if update is required based on hash
|
|
if currentHash and currentHash == remoteHash then
|
|
return false
|
|
end
|
|
|
|
-- Ok, let's do this
|
|
local baseUrl = urlDir(uri)
|
|
text = share.setFrontmatter({
|
|
uri = uri,
|
|
hash = remoteHash,
|
|
mode = "pull"
|
|
}, text)
|
|
|
|
-- First write the main file
|
|
space.writePage(pageName, text)
|
|
|
|
-- And download and write assets, if any
|
|
if remoteLibFM.files then
|
|
for file in remoteLibFM.files do
|
|
print("Downloading file", file)
|
|
local targetPath = pageName .. "/" .. file
|
|
local data = net.readURI(baseUrl .. file, {encoding="application/octet-stream"})
|
|
if not data then
|
|
error("Could not fetch file: "..baseUrl..file)
|
|
end
|
|
space.writeFile(targetPath, data)
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
-- Update a library
|
|
function library.update(pageName, force)
|
|
local text = space.readPage(pageName)
|
|
local fm = index.extractFrontmatter(text).frontmatter
|
|
if fm.share then
|
|
return library.install(pageName, fm.share.uri, fm.share.hash)
|
|
else
|
|
print("No 'share' key found in frontmatter for " .. pageName)
|
|
end
|
|
end
|
|
|
|
function library.remove(pageName)
|
|
local text = space.readPage(pageName)
|
|
|
|
if not text then
|
|
error("Could not read: " .. pageName)
|
|
end
|
|
|
|
local fm = index.extractFrontmatter(text).frontmatter
|
|
|
|
-- Remove associated files first
|
|
if fm.files then
|
|
for file in fm.files do
|
|
local p = pageName .. "/" .. file
|
|
space.deleteFile(p)
|
|
end
|
|
end
|
|
|
|
-- Then page itself
|
|
space.deletePage(pageName)
|
|
end
|
|
|
|
function library.installedLibrariesWidget()
|
|
local rows = {}
|
|
for lib in query[[from index.tag "meta/library"]] do
|
|
table.insert(rows, dom.tr {
|
|
dom.td { "[[" .. lib.name .. "|" .. lib.name .. "]]" },
|
|
dom.td {
|
|
not lib.share and "" or dom.span{
|
|
widgets.button("Update", function()
|
|
local updated = library.update(lib.name, true)
|
|
if updated then
|
|
editor.flashNotification "Updated."
|
|
reloadEverything()
|
|
else
|
|
editor.flashNotification "No update required."
|
|
end
|
|
end),
|
|
widgets.button("Remove", function()
|
|
if editor.confirm("Are you sure?") then
|
|
library.remove(lib.name)
|
|
editor.flashNotification "Done!"
|
|
reloadEverything()
|
|
end
|
|
end)
|
|
}
|
|
}
|
|
})
|
|
end
|
|
return widget.htmlBlock(dom.table {
|
|
dom.thead {
|
|
dom.tr {
|
|
dom.td {"Library"},
|
|
dom.td {"Action"}
|
|
}
|
|
},
|
|
dom.tbody(rows)
|
|
})
|
|
end
|
|
|
|
function library.installableLibrariesWidget()
|
|
local rows = {}
|
|
for lib in query[[from index.tag "meta/library/remote"]] do
|
|
local installed = library.getInstalled(lib.uri)
|
|
if not installed then
|
|
table.insert(rows, dom.tr {
|
|
dom.td { lib.name },
|
|
dom.td { "[[" .. lib.page .. "|" .. lib.page .. "]]" },
|
|
dom.td {
|
|
widgets.button("Install", function()
|
|
library.install(library.libPath(lib.name), lib.uri)
|
|
editor.flashNotification "Done!"
|
|
reloadEverything()
|
|
end)
|
|
}
|
|
})
|
|
end
|
|
end
|
|
return widget.htmlBlock(dom.table {
|
|
dom.thead {
|
|
dom.tr {
|
|
dom.td {"Library"},
|
|
dom.td {"Repository"},
|
|
dom.td {"Action"}
|
|
}
|
|
},
|
|
dom.tbody(rows)
|
|
})
|
|
end
|
|
|
|
function library.installedRepositoriesWidget()
|
|
local rows = {}
|
|
for repo in query[[from index.tag(library.repositoryTag)]] do
|
|
table.insert(rows, dom.tr {
|
|
dom.td { "[[" .. repo.name .. "|" .. repo.name .. "]]" },
|
|
dom.td {
|
|
repo.share and dom.span {
|
|
widgets.button("Update", function()
|
|
if share.sharePage(repo.name) then
|
|
editor.flashNotification "Repository updated"
|
|
reloadEverything()
|
|
else
|
|
editor.flashNotification "No changes"
|
|
end
|
|
end),
|
|
widgets.button("Remove", function()
|
|
if editor.confirm("Are you sure?") then
|
|
space.deletePage(repo.name)
|
|
editor.flashNotification "Done!"
|
|
reloadEverything()
|
|
end
|
|
end)
|
|
} or ""
|
|
}
|
|
})
|
|
end
|
|
return widget.htmlBlock(dom.table {
|
|
dom.thead {
|
|
dom.tr {
|
|
dom.td {"Repository"},
|
|
dom.td {"Action"}
|
|
}
|
|
},
|
|
dom.tbody(rows)
|
|
})
|
|
end
|
|
|
|
-- Utility functions
|
|
local function urlDir(url)
|
|
return string.match(url, "^(.*/)[^/]+$")
|
|
end
|
|
|
|
-- Construct full library path from name
|
|
function library.libPath(name)
|
|
return libraryPrefix .. name
|
|
end
|
|
|
|
-- Extract name from full path
|
|
function library.libName(path)
|
|
return path:sub(#libraryPrefix + 1)
|
|
end
|
|
|
|
function library.repoName(path)
|
|
return path:sub(#repositoryPrefix + 1)
|
|
end
|
|
|
|
function reloadEverything()
|
|
mq.awaitEmptyQueue("indexQueue")
|
|
editor.reloadConfigAndCommands()
|
|
codeWidget.refreshAll()
|
|
end
|
|
|
|
```
|