249 lines
4.6 KiB
Lua
249 lines
4.6 KiB
Lua
|
local gc = require 'gc'
|
||
|
|
||
|
---@class scope.manager
|
||
|
local m = {}
|
||
|
|
||
|
---@alias scope.type '"override"'|'"folder"'|'"fallback"'
|
||
|
|
||
|
---@class scope
|
||
|
---@field type scope.type
|
||
|
---@field uri? uri
|
||
|
---@field _links table<uri, boolean>
|
||
|
---@field _data table<string, any>
|
||
|
---@field _gc gc
|
||
|
---@field _removed? true
|
||
|
local mt = {}
|
||
|
mt.__index = mt
|
||
|
|
||
|
function mt:__tostring()
|
||
|
if self.uri then
|
||
|
return ('{scope|%s|%s}'):format(self.type, self.uri)
|
||
|
else
|
||
|
return ('{scope|%s}'):format(self.type)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
---@param uri uri
|
||
|
function mt:addLink(uri)
|
||
|
self._links[uri] = true
|
||
|
end
|
||
|
|
||
|
---@param uri uri
|
||
|
function mt:removeLink(uri)
|
||
|
self._links[uri] = nil
|
||
|
end
|
||
|
|
||
|
function mt:removeAllLinks()
|
||
|
self._links = {}
|
||
|
end
|
||
|
|
||
|
---@return fun(): uri
|
||
|
---@return table<uri, true>
|
||
|
function mt:eachLink()
|
||
|
return next, self._links
|
||
|
end
|
||
|
|
||
|
---@param uri uri
|
||
|
---@return boolean
|
||
|
function mt:isChildUri(uri)
|
||
|
if not uri then
|
||
|
return false
|
||
|
end
|
||
|
if not self.uri then
|
||
|
return false
|
||
|
end
|
||
|
if self.uri == uri then
|
||
|
return true
|
||
|
end
|
||
|
if uri:sub(1, #self.uri) ~= self.uri then
|
||
|
return false
|
||
|
end
|
||
|
if uri:sub(#self.uri, #self.uri) == '/'
|
||
|
or uri:sub(#self.uri + 1, #self.uri + 1) == '/' then
|
||
|
return true
|
||
|
end
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
---@param uri uri
|
||
|
---@return boolean
|
||
|
function mt:isLinkedUri(uri)
|
||
|
if not uri then
|
||
|
return false
|
||
|
end
|
||
|
for linkUri in pairs(self._links) do
|
||
|
if uri == linkUri then
|
||
|
return true
|
||
|
end
|
||
|
if uri:sub(1, #linkUri) ~= linkUri then
|
||
|
goto CONTINUE
|
||
|
end
|
||
|
if uri:sub(#linkUri, #linkUri) == '/'
|
||
|
or uri:sub(#linkUri + 1, #linkUri + 1) == '/' then
|
||
|
return true
|
||
|
end
|
||
|
::CONTINUE::
|
||
|
end
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
---@param uri uri
|
||
|
---@return boolean
|
||
|
function mt:isVisible(uri)
|
||
|
return self:isChildUri(uri)
|
||
|
or self:isLinkedUri(uri)
|
||
|
or self == m.getScope(uri)
|
||
|
end
|
||
|
|
||
|
---@param uri uri
|
||
|
---@return uri?
|
||
|
function mt:getLinkedUri(uri)
|
||
|
if not uri then
|
||
|
return nil
|
||
|
end
|
||
|
for linkUri in pairs(self._links) do
|
||
|
if uri:sub(1, #linkUri) == linkUri then
|
||
|
return linkUri
|
||
|
end
|
||
|
end
|
||
|
return nil
|
||
|
end
|
||
|
|
||
|
---@param uri uri
|
||
|
---@return uri?
|
||
|
function mt:getRootUri(uri)
|
||
|
if self:isChildUri(uri) then
|
||
|
return self.uri
|
||
|
end
|
||
|
return self:getLinkedUri(uri)
|
||
|
end
|
||
|
|
||
|
---@param k string
|
||
|
---@param v any
|
||
|
function mt:set(k, v)
|
||
|
self._data[k] = v
|
||
|
return v
|
||
|
end
|
||
|
|
||
|
---@param k string
|
||
|
---@return any
|
||
|
function mt:get(k)
|
||
|
return self._data[k]
|
||
|
end
|
||
|
|
||
|
---@return string
|
||
|
function mt:getName()
|
||
|
return self.uri or ('<' .. self.type .. '>')
|
||
|
end
|
||
|
|
||
|
function mt:gc(obj)
|
||
|
self._gc:add(obj)
|
||
|
end
|
||
|
|
||
|
function mt:flushGC()
|
||
|
self._gc:remove()
|
||
|
if self._removed then
|
||
|
return
|
||
|
end
|
||
|
self._gc = gc()
|
||
|
end
|
||
|
|
||
|
function mt:remove()
|
||
|
if self._removed then
|
||
|
return
|
||
|
end
|
||
|
self._removed = true
|
||
|
for i, scp in ipairs(m.folders) do
|
||
|
if scp == self then
|
||
|
table.remove(m.folders, i)
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
self:flushGC()
|
||
|
end
|
||
|
|
||
|
function mt:isRemoved()
|
||
|
return self._removed == true
|
||
|
end
|
||
|
|
||
|
---@param scopeType scope.type
|
||
|
---@return scope
|
||
|
local function createScope(scopeType)
|
||
|
local scope = setmetatable({
|
||
|
type = scopeType,
|
||
|
_links = {},
|
||
|
_data = {},
|
||
|
_gc = gc(),
|
||
|
}, mt)
|
||
|
|
||
|
return scope
|
||
|
end
|
||
|
|
||
|
function m.reset()
|
||
|
---@type scope[]
|
||
|
m.folders = {}
|
||
|
m.override = createScope 'override'
|
||
|
m.fallback = createScope 'fallback'
|
||
|
end
|
||
|
|
||
|
m.reset()
|
||
|
|
||
|
---@param uri uri
|
||
|
---@return scope
|
||
|
function m.createFolder(uri)
|
||
|
local scope = createScope 'folder'
|
||
|
scope.uri = uri
|
||
|
|
||
|
local inserted = false
|
||
|
for i, otherScope in ipairs(m.folders) do
|
||
|
if #uri > #otherScope.uri then
|
||
|
table.insert(m.folders, i, scope)
|
||
|
inserted = true
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
if not inserted then
|
||
|
table.insert(m.folders, scope)
|
||
|
end
|
||
|
|
||
|
return scope
|
||
|
end
|
||
|
|
||
|
---@param uri uri
|
||
|
---@return scope?
|
||
|
function m.getFolder(uri)
|
||
|
for _, scope in ipairs(m.folders) do
|
||
|
if scope:isChildUri(uri) then
|
||
|
return scope
|
||
|
end
|
||
|
end
|
||
|
return nil
|
||
|
end
|
||
|
|
||
|
---@param uri uri
|
||
|
---@return scope?
|
||
|
function m.getLinkedScope(uri)
|
||
|
if m.override and m.override:isLinkedUri(uri) then
|
||
|
return m.override
|
||
|
end
|
||
|
for _, scope in ipairs(m.folders) do
|
||
|
if scope:isLinkedUri(uri) then
|
||
|
return scope
|
||
|
end
|
||
|
end
|
||
|
if m.fallback:isLinkedUri(uri) then
|
||
|
return m.fallback
|
||
|
end
|
||
|
return nil
|
||
|
end
|
||
|
|
||
|
---@param uri uri
|
||
|
---@return scope
|
||
|
function m.getScope(uri)
|
||
|
return m.getFolder(uri)
|
||
|
or m.getLinkedScope(uri)
|
||
|
or m.fallback
|
||
|
end
|
||
|
|
||
|
return m
|