summaryrefslogtreecommitdiff
path: root/catalogue.lua
blob: 7bd8eb02164d6e09e433ca330242770d6091883c (plain) (blame)
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