dotfiles/.aegisub/automation/include/myaa/pl/OrderedMap.lua

168 lines
4.4 KiB
Lua

--- OrderedMap, a map which preserves ordering.
--
-- Derived from `pl.Map`.
--
-- Dependencies: `pl.utils`, `pl.tablex`, `pl.class`, `pl.List`, `pl.Map`
-- @classmod pl.OrderedMap
local tablex = require 'pl.tablex'
local utils = require 'pl.utils'
local List = require 'pl.List'
local index_by,tsort,concat = tablex.index_by,table.sort,table.concat
local class = require 'pl.class'
local Map = require 'pl.Map'
local OrderedMap = class(Map)
OrderedMap._name = 'OrderedMap'
local rawset = rawset
--- construct an OrderedMap.
-- Will throw an error if the argument is bad.
-- @param t optional initialization table, same as for @{OrderedMap:update}
function OrderedMap:_init (t)
rawset(self,'_keys',List())
if t then
local map,err = self:update(t)
if not map then error(err,2) end
end
end
local assert_arg,raise = utils.assert_arg,utils.raise
--- update an OrderedMap using a table.
-- If the table is itself an OrderedMap, then its entries will be appended.
-- if it s a table of the form `{{key1=val1},{key2=val2},...}` these will be appended.
--
-- Otherwise, it is assumed to be a map-like table, and order of extra entries is arbitrary.
-- @tab t a table.
-- @return the map, or nil in case of error
-- @return the error message
function OrderedMap:update (t)
assert_arg(1,t,'table')
if OrderedMap:class_of(t) then
for k,v in t:iter() do
self:set(k,v)
end
elseif #t > 0 then -- an array must contain {key=val} tables
if type(t[1]) == 'table' then
for _,pair in ipairs(t) do
local key,value = next(pair)
if not key then return raise 'empty pair initialization table' end
self:set(key,value)
end
else
return raise 'cannot use an array to initialize an OrderedMap'
end
else
for k,v in pairs(t) do
self:set(k,v)
end
end
return self
end
--- set the key's value. This key will be appended at the end of the map.
--
-- If the value is nil, then the key is removed.
-- @param key the key
-- @param val the value
-- @return the map
function OrderedMap:set (key,val)
if rawget(self, key) == nil and val ~= nil then -- new key
self._keys:append(key) -- we keep in order
rawset(self,key,val) -- don't want to provoke __newindex!
else -- existing key-value pair
if val == nil then
self._keys:remove_value(key)
rawset(self,key,nil)
else
self[key] = val
end
end
return self
end
OrderedMap.__newindex = OrderedMap.set
--- insert a key/value pair before a given position.
-- Note: if the map already contains the key, then this effectively
-- moves the item to the new position by first removing at the old position.
-- Has no effect if the key does not exist and val is nil
-- @int pos a position starting at 1
-- @param key the key
-- @param val the value; if nil use the old value
function OrderedMap:insert (pos,key,val)
local oldval = self[key]
val = val or oldval
if oldval then
self._keys:remove_value(key)
end
if val then
self._keys:insert(pos,key)
rawset(self,key,val)
end
return self
end
--- return the keys in order.
-- (Not a copy!)
-- @return List
function OrderedMap:keys ()
return self._keys
end
--- return the values in order.
-- this is relatively expensive.
-- @return List
function OrderedMap:values ()
return List(index_by(self,self._keys))
end
--- sort the keys.
-- @func cmp a comparison function as for @{table.sort}
-- @return the map
function OrderedMap:sort (cmp)
tsort(self._keys,cmp)
return self
end
--- iterate over key-value pairs in order.
function OrderedMap:iter ()
local i = 0
local keys = self._keys
local idx
return function()
i = i + 1
if i > #keys then return nil end
idx = keys[i]
return idx,self[idx]
end
end
--- iterate over an ordered map (5.2).
-- @within metamethods
-- @function OrderedMap:__pairs
OrderedMap.__pairs = OrderedMap.iter
--- string representation of an ordered map.
-- @within metamethods
function OrderedMap:__tostring ()
local res = {}
for i,v in ipairs(self._keys) do
local val = self[v]
local vs = tostring(val)
if type(val) ~= 'number' then
vs = '"'..vs..'"'
end
res[i] = tostring(v)..'='..vs
end
return '{'..concat(res,',')..'}'
end
return OrderedMap