Forge
csharp3407750f
1using Tomlyn;
2using Tomlyn.Model;
3using AgentForge.Plugin.Sdk;
4
5namespace AgentForge.Plugin.RepositoryImport.GitHub.Core;
6
7public sealed class ForgeImportCatalogDocument
8{
9 public ForgeImportCatalogSettings Catalog { get; set; } = new();
10
11 public List<ForgeImportCatalogExplicit> Explicit { get; set; } = [];
12
13 public List<ForgeImportCatalogRule> Rules { get; set; } = [];
14
15 public List<ForgeImportCatalogTopicRule> TopicRules { get; set; } = [];
16}
17
18public sealed class ForgeImportCatalogSettings
19{
20 public string ForgeOrg { get; set; } = "";
21
22 public string GitHubOrg { get; set; } = "";
23
24 public string UnmatchedCatalog { get; set; } = "backup/unclassified";
25
26 public string UnmatchedVisibility { get; set; } = "private";
27
28 public List<string> SkipRepos { get; set; } = [];
29
30 public List<string> EnsureGroups { get; set; } = [];
31}
32
33public sealed class ForgeImportCatalogExplicit
34{
35 public string GitHubName { get; set; } = "";
36
37 public string? ForgeSlug { get; set; }
38
39 public string CatalogPath { get; set; } = "";
40
41 public string? Visibility { get; set; }
42}
43
44public sealed class ForgeImportCatalogRule
45{
46 public string Id { get; set; } = "";
47
48 public string? NameExact { get; set; }
49
50 public List<string> NameOneOf { get; set; } = [];
51
52 public string? NamePrefix { get; set; }
53
54 public string? NameSuffix { get; set; }
55
56 public string? CatalogPath { get; set; }
57
58 public string? CatalogPathTemplate { get; set; }
59
60 public string? Visibility { get; set; }
61}
62
63public sealed class ForgeImportCatalogTopicRule
64{
65 public List<string> TopicsAll { get; set; } = [];
66
67 public string CatalogPath { get; set; } = "";
68
69 public string? Visibility { get; set; }
70}
71
72public sealed class ForgeImportCatalogLoader
73{
74 public ForgeImportCatalogDocument Load(string path)
75 {
76 if (!File.Exists(path))
77 throw new ForgeApiException(404, $"Import catalog config not found: '{path}'.");
78
79 var model = Toml.ToModel(File.ReadAllText(path));
80 var doc = new ForgeImportCatalogDocument();
81 if (model.TryGetValue("catalog", out var catalogObj) && catalogObj is TomlTable catalogTable)
82 doc.Catalog = ReadSettings(catalogTable);
83
84 ReadArraySection(model, "catalog", "explicit", table => doc.Explicit.Add(ReadExplicit(table)));
85 ReadArraySection(model, "catalog", "rules", table => doc.Rules.Add(ReadRule(table)));
86 ReadArraySection(model, "catalog", "topic_rules", table => doc.TopicRules.Add(ReadTopicRule(table)));
87
88 return doc;
89 }
90
91 public string ResolveDefaultPath(string contentRoot, string? forgeOrg)
92 {
93 var slug = string.IsNullOrWhiteSpace(forgeOrg) ? "ai-guiders" : forgeOrg.Trim();
94 var candidates = new[]
95 {
96 Path.Combine(contentRoot, "config", $"import-catalog.{slug}.toml"),
97 Path.Combine(contentRoot, "config", "import-catalog.ai-guiders.toml"),
98 Path.Combine(AppContext.BaseDirectory, "config", $"import-catalog.{slug}.toml"),
99 Path.Combine(AppContext.BaseDirectory, "config", "import-catalog.ai-guiders.toml"),
100 };
101
102 return candidates.FirstOrDefault(File.Exists) ?? candidates[0];
103 }
104
105 private static void ReadArraySection(
106 TomlTable model,
107 string root,
108 string key,
109 Action<TomlTable> read)
110 {
111 if (!model.TryGetValue(root, out var rootObj) || rootObj is not TomlTable rootTable)
112 return;
113 if (!rootTable.TryGetValue(key, out var arrayObj) || arrayObj is not TomlTableArray array)
114 return;
115 foreach (var table in array)
116 read(table);
117 }
118
119 private static ForgeImportCatalogSettings ReadSettings(TomlTable table)
120 {
121 var settings = new ForgeImportCatalogSettings();
122 if (table.TryGetValue("forge_org", out var forgeObj) && forgeObj is string forge)
123 settings.ForgeOrg = forge.Trim();
124 if (table.TryGetValue("github_org", out var ghObj) && ghObj is string gh)
125 settings.GitHubOrg = gh.Trim();
126 if (table.TryGetValue("unmatched_catalog", out var unmatchedObj) && unmatchedObj is string unmatched)
127 settings.UnmatchedCatalog = unmatched.Trim();
128 if (table.TryGetValue("unmatched_visibility", out var visObj) && visObj is string vis)
129 settings.UnmatchedVisibility = vis.Trim();
130 settings.SkipRepos = ReadStringList(table, "skip_repos");
131 settings.EnsureGroups = ReadStringList(table, "ensure_groups");
132 return settings;
133 }
134
135 private static ForgeImportCatalogExplicit ReadExplicit(TomlTable table) => new()
136 {
137 GitHubName = ReadString(table, "github_name") ?? "",
138 ForgeSlug = ReadString(table, "forge_slug"),
139 CatalogPath = ReadString(table, "catalog_path") ?? "",
140 Visibility = ReadString(table, "visibility"),
141 };
142
143 private static ForgeImportCatalogRule ReadRule(TomlTable table) => new()
144 {
145 Id = ReadString(table, "id") ?? "",
146 NameExact = ReadString(table, "name_exact"),
147 NameOneOf = ReadStringList(table, "name_one_of"),
148 NamePrefix = ReadString(table, "name_prefix"),
149 NameSuffix = ReadString(table, "name_suffix"),
150 CatalogPath = ReadString(table, "catalog_path"),
151 CatalogPathTemplate = ReadString(table, "catalog_path_template"),
152 Visibility = ReadString(table, "visibility"),
153 };
154
155 private static ForgeImportCatalogTopicRule ReadTopicRule(TomlTable table) => new()
156 {
157 TopicsAll = ReadStringList(table, "topics_all"),
158 CatalogPath = ReadString(table, "catalog_path") ?? "",
159 Visibility = ReadString(table, "visibility"),
160 };
161
162 private static string? ReadString(TomlTable table, string key) =>
163 table.TryGetValue(key, out var obj) && obj is string value && !string.IsNullOrWhiteSpace(value)
164 ? value.Trim()
165 : null;
166
167 private static List<string> ReadStringList(TomlTable table, string key)
168 {
169 if (!table.TryGetValue(key, out var obj))
170 return [];
171 return obj switch
172 {
173 TomlArray array => array.OfType<string>().Select(s => s.Trim()).Where(s => s.Length > 0).ToList(),
174 string single when single.Length > 0 => [single.Trim()],
175 _ => [],
176 };
177 }
178}
179
View only · write via MCP/CIDE