1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
|
-- 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(src_id, section)
local id = src_id .. ':' .. section .. ':cat:catchall'
local entry = self.data[id]
if entry then
return entry
end
return self:add({
src_id = src_id,
section = section,
type = 'group',
group_type = 'cat',
id = id,
parent_id = src_id .. ':' .. section .. ':cat: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.src_id, 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:prune_children(entry, is_child)
for _, v in ipairs(entry.children) do
self.data[v.id] = nil
if v.children then
self:prune_children(v, true)
end
end
if not is_child then
entry.children = {}
end
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
|