/****************************************************************************** __ ____ __ / / ___ ____ _/ __/_____________ _/ /__ / / / _ \/ __ `/ /_/ ___/ ___/ __ `/ / _ \ / /___/ __/ /_/ / __(__ ) /__/ /_/ / / __/ /_____/\___/\__,_/_/ /____/\___/\__,_/_/\___/ (C)opyright 2025, Leafscale, LLC - https://www.leafscale.com Project: Zygaena Filename: groups.reef Authors: Chris Tusa License: 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