using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Threading; using AxCopilot.SDK; using AxCopilot.Services; namespace AxCopilot.Handlers; public class ScaffoldHandler : IActionHandler { internal record ScaffoldTemplate([property: JsonPropertyName("name")] string Name, [property: JsonPropertyName("description")] string Description, [property: JsonPropertyName("paths")] string[] Paths); private static readonly string TemplateDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "templates"); private static readonly ScaffoldTemplate[] BuiltInTemplates = new ScaffoldTemplate[5] { new ScaffoldTemplate("webapi", "Web API 프로젝트", new string[8] { "src/Controllers/", "src/Models/", "src/Services/", "src/Middleware/", "tests/", "docs/", "README.md", ".gitignore" }), new ScaffoldTemplate("console", "콘솔 애플리케이션", new string[5] { "src/", "src/Core/", "src/Services/", "tests/", "README.md" }), new ScaffoldTemplate("wpf", "WPF 데스크톱 앱", new string[8] { "src/Views/", "src/ViewModels/", "src/Models/", "src/Services/", "src/Themes/", "src/Assets/", "tests/", "docs/" }), new ScaffoldTemplate("data", "데이터 파이프라인", new string[9] { "src/Extractors/", "src/Transformers/", "src/Loaders/", "config/", "scripts/", "tests/", "data/input/", "data/output/", "README.md" }), new ScaffoldTemplate("docs", "문서 프로젝트", new string[5] { "docs/", "images/", "templates/", "README.md", "CHANGELOG.md" }) }; public string? Prefix => "scaffold"; public PluginMetadata Metadata => new PluginMetadata("Scaffold", "프로젝트 스캐폴딩 — scaffold", "1.0", "AX"); public Task> GetItemsAsync(string query, CancellationToken ct) { string q = query.Trim(); IEnumerable second = LoadUserTemplates(); List list = BuiltInTemplates.Concat(second).ToList(); if (string.IsNullOrWhiteSpace(q)) { List list2 = list.Select((ScaffoldTemplate t) => new LauncherItem("[" + t.Name + "] " + t.Description, $"{t.Paths.Length}개 폴더/파일 · Enter → 대상 경로 입력 후 생성", null, t, null, "\ue8b7")).ToList(); list2.Insert(0, new LauncherItem("프로젝트 스캐폴딩", $"총 {list.Count}개 템플릿 · 이름을 입력해 필터링", null, null, null, "\ue946")); return Task.FromResult((IEnumerable)list2); } if (q.Contains('\\') || q.Contains('/')) { int num = q.LastIndexOf(' '); if (num > 0) { string text = q.Substring(0, num).Trim(); string text2 = q; int num2 = num + 1; string templateName = text2.Substring(num2, text2.Length - num2).Trim(); ScaffoldTemplate scaffoldTemplate = list.FirstOrDefault((ScaffoldTemplate t) => t.Name.Equals(templateName, StringComparison.OrdinalIgnoreCase)); if (scaffoldTemplate != null) { return Task.FromResult((IEnumerable)new _003C_003Ez__ReadOnlySingleElementList(new LauncherItem("[" + scaffoldTemplate.Name + "] → " + text, $"{scaffoldTemplate.Paths.Length}개 폴더/파일 생성 · Enter로 실행", null, ValueTuple.Create(text, scaffoldTemplate), null, "\ue74e"))); } } } IEnumerable source = list.Where((ScaffoldTemplate t) => t.Name.Contains(q, StringComparison.OrdinalIgnoreCase) || t.Description.Contains(q, StringComparison.OrdinalIgnoreCase)); List list3 = source.Select(delegate(ScaffoldTemplate t) { string text3 = string.Join(", ", t.Paths.Take(4)); if (t.Paths.Length > 4) { text3 += $" ... (+{t.Paths.Length - 4})"; } return new LauncherItem("[" + t.Name + "] " + t.Description, text3 + " · 사용법: scaffold [대상경로] " + t.Name, null, t, null, "\ue8b7"); }).ToList(); if (!list3.Any()) { list3.Add(new LauncherItem("'" + q + "'에 해당하는 템플릿 없음", "scaffold 으로 전체 목록 확인", null, null, null, "\ue7ba")); } return Task.FromResult((IEnumerable)list3); } public Task ExecuteAsync(LauncherItem item, CancellationToken ct) { if (!(item.Data is (string, ScaffoldTemplate) tuple)) { if (item.Data is ScaffoldTemplate scaffoldTemplate) { string usage = "scaffold [대상경로] " + scaffoldTemplate.Name; try { Application current = Application.Current; if (current != null) { ((DispatcherObject)current).Dispatcher.Invoke((Action)delegate { Clipboard.SetText(usage); }); } } catch { } } return Task.CompletedTask; } var (basePath, template) = tuple; return CreateStructure(basePath, template); } private static Task CreateStructure(string basePath, ScaffoldTemplate template) { try { int num = 0; string[] paths = template.Paths; foreach (string text in paths) { string path = Path.Combine(basePath, text.Replace('/', Path.DirectorySeparatorChar)); if (text.EndsWith('/') || text.EndsWith('\\') || !Path.HasExtension(text)) { Directory.CreateDirectory(path); } else { Directory.CreateDirectory(Path.GetDirectoryName(path)); if (!File.Exists(path)) { File.WriteAllText(path, ""); } } num++; } NotificationService.Notify("스캐폴딩 완료", $"[{template.Name}] {num}개 항목 생성 → {basePath}"); } catch (Exception ex) { LogService.Error("스캐폴딩 실패: " + ex.Message); NotificationService.Notify("AX Copilot", "스캐폴딩 실패: " + ex.Message); } return Task.CompletedTask; } private static IEnumerable LoadUserTemplates() { if (!Directory.Exists(TemplateDir)) { yield break; } string[] files = Directory.GetFiles(TemplateDir, "*.json"); foreach (string file in files) { ScaffoldTemplate tmpl = null; try { string json = File.ReadAllText(file); tmpl = JsonSerializer.Deserialize(json); } catch { } if (tmpl != null) { yield return tmpl; } } } }