-- Copyright 2025 David Vazgenovich Shakaryan local util = require('util') local catalogue = {} local mt = {} mt.__index = mt function catalogue.new() local t = setmetatable({data = {}}, mt) t:add({ type = 'group', id = 'root', name = '/', }) return t end function mt:get(id) return self.data[id] end function mt:_ensure_catchall(section_id) local id = section_id .. ':category:catchall' local entry = self.data[id] if entry then return entry end return self:add({ section = section_id, type = 'group', group_type = 'category', id = id, parent_id = section_id .. ':category:0', -- non-ascii symbol to sort near end name = '∗CATCHALL∗', }) end function mt:add(entry) self.data[entry.id] = entry if entry.type == 'group' and not entry.children_f then entry.children = {} end if not entry.parent_id then return entry end local parent = self.data[entry.parent_id] -- dump any entries referencing nonexistent categories into a single -- catchall category if not parent then parent = self:_ensure_catchall(entry.section) entry.parent_id = parent.id end if parent.id == entry.id then entry.parent_id = nil else parent.children[#parent.children+1] = entry end return entry end function mt:path_to_root(entry) local path = {} local curr = entry while curr.parent_id and curr.parent_id ~= 'root' do curr = self:get(curr.parent_id) if not curr then return end path[#path+1] = curr end if curr.parent_id ~= 'root' then return end return path end function mt:path_from_root(entry) local path = self:path_to_root(entry) if path then util.reverse(path) end return path end function mt:group_count(group) if group.count then return group.count end if group.count_f then return group.count_f(group) end local count = 0 -- only children, not children_f, is considered here. dynamically -- loaded groups will have a count of 0 unless count or count_f is -- specified. for _, v in ipairs(group.children or {}) do if v.type == 'group' then count = count + (v.count or mt:group_count(v) or 0) else count = count + 1 end end return count end function mt:group_children(group) return group.children_f and group.children_f(group) or group.children end return catalogue