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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
|
/******************************************************************************
__ ____ __
/ / ___ ____ _/ __/_____________ _/ /__
/ / / _ \/ __ `/ /_/ ___/ ___/ __ `/ / _ \
/ /___/ __/ /_/ / __(__ ) /__/ /_/ / / __/
/_____/\___/\__,_/_/ /____/\___/\__,_/_/\___/
(C)opyright 2025, Leafscale, LLC - https://www.leafscale.com
Project: Zygaena
Filename: groups.reef
Authors: Chris Tusa <chris.tusa@leafscale.com>
License: <see LICENSE file included with this source code>
Description: Package groups support for Coral (@groupname)
******************************************************************************/
module core.groups
import core.str
import io.file
import io.dir
import io.path
import encoding.toml
import types
import core.result as res
export
type GroupResult
fn load_group(name: string): GroupResult
fn list_groups(groups: [types.PackageGroup], max_count: int): int
fn expand_group(name: string, packages: [string], max_count: int): int
fn is_group(name: string): bool
end export
// Result for group loading
type GroupResult = struct
success: bool
group: types.PackageGroup
error: string
end GroupResult
// Get the groups directory
fn get_groups_dir(): string
return types.get_groups_dir()
end get_groups_dir
// Create empty group
fn new_group(): types.PackageGroup
return types.new_package_group()
end new_group
// Create success result
fn success_result(g: types.PackageGroup): GroupResult
return GroupResult{ success: true, group: g, error: "" }
end success_result
// Create error result
fn error_result(msg: string): GroupResult
return GroupResult{ success: false, group: new_group(), error: msg }
end error_result
// Check if a package name is a group reference (starts with @)
fn is_group(name: string): bool
if str.length(name) < 2
return false
end if
return name[0] == '@'
end is_group
// Load a group definition from TOML file
fn load_group(name: string): GroupResult
// Remove @ prefix if present
mut group_name = name
if name[0] == '@'
group_name = str.substring(name, 1, str.length(name) - 1)
end if
let groups_dir = get_groups_dir()
let group_file = path.join_path(groups_dir, group_name + ".toml")
if not file.fileExists(group_file)
return error_result("Group not found: " + group_name)
end if
let content = res.unwrap_or(file.readFile(group_file), "")
if str.length(content) == 0
return error_result("Failed to read group file")
end if
// Parse TOML using key/value arrays
let keys = toml.toml_alloc_keys()
let vals = toml.toml_alloc_values()
let entry_count = res.unwrap_or(toml.toml_parse(content, keys, vals), 0)
if entry_count == 0
return error_result("Invalid group file format")
end if
mut group = new_group()
group.name = group_name
// Get description
if toml.toml_has_key(keys, vals, entry_count, "group.description")
group.description = toml.toml_get(keys, vals, entry_count, "group.description")
end if
// Get members list (packages array)
if toml.toml_has_key(keys, vals, entry_count, "group.packages")
let members_str = toml.toml_get(keys, vals, entry_count, "group.packages")
group.members_count = parse_toml_array(members_str, group.members, 256)
end if
return success_result(group)
end load_group
// List all available groups
fn list_groups(groups: [types.PackageGroup], max_count: int): int
let groups_dir = get_groups_dir()
if not dir.dir_exists(groups_dir)
return 0
end if
let entries = res.unwrap_or(dir.list_dir(groups_dir), new [string](0))
let entry_count = entries.length()
mut count = 0
mut i = 0
while i < entry_count and count < max_count
let entry = entries[i]
// Look for .toml files
if str.ends_with(entry, ".toml")
let name_len = str.length(entry) - 5
let name = str.substring(entry, 0, name_len)
let result = load_group(name)
if result.success
groups[count] = result.group
count = count + 1
end if
end if
i = i + 1
end while
return count
end list_groups
// Expand a group to its member packages
fn expand_group(name: string, packages: [string], max_count: int): int
let result = load_group(name)
if not result.success
return 0
end if
let group = result.group
mut count = 0
while count < group.members_count and count < max_count
packages[count] = group.members[count]
count = count + 1
end while
return count
end expand_group
// Parse TOML array value into string array
// Format: ["item1", "item2", "item3"]
fn parse_toml_array(value: string, result: [string], max_items: int): int
let len = str.length(value)
if len < 2
return 0
end if
// Check for array brackets
if value[0] != '['
return 0
end if
mut count = 0
mut i = 1
mut in_string = false
mut item_start = 0
while i < len and count < max_items
let c = value[i]
if c == '"' and not in_string
in_string = true
item_start = i + 1
elif c == '"' and in_string
// End of string item
let item_len = i - item_start
if item_len > 0
result[count] = str.substring(value, item_start, item_len)
count = count + 1
end if
in_string = false
elif c == ']' and not in_string
// End of array
return count
end if
i = i + 1
end while
return count
end parse_toml_array
end module
|