@@ -3,6 +3,11 @@ package config
33import (
44 _ "embed"
55 "fmt"
6+ "io"
7+ "os"
8+ "os/exec"
9+ "path/filepath"
10+ "strings"
611
712 "gopkg.in/yaml.v3"
813)
@@ -21,8 +26,9 @@ type Preset struct {
2126}
2227
2328type TemplatesConfig struct {
29+ Repo string `yaml:"repo"`
2430 Templates map [string ]map [string ]TemplateConfig `yaml:"templates"`
25- Presets map [string ][]Preset `yaml:"presets"`
31+ Presets map [string ][]Preset `yaml:"presets,omitempty "`
2632}
2733
2834//go:embed templates.yaml
@@ -65,19 +71,37 @@ func GetTemplate(language, templateKey string) (*TemplateConfig, error) {
6571 return & template , nil
6672}
6773
74+ // NeedsTemplateDiscovery checks if templates need to be discovered from repository
75+ func NeedsTemplateDiscovery (language string ) (bool , error ) {
76+ config , err := LoadTemplatesConfig ()
77+ if err != nil {
78+ return false , err
79+ }
80+
81+ // If presets are defined in YAML, no need to discover
82+ if presets , ok := config .Presets [language ]; ok && len (presets ) > 0 {
83+ return false , nil
84+ }
85+
86+ // Otherwise, need to discover templates from repository
87+ return true , nil
88+ }
89+
6890// GetPresets gets preset list for specified language
91+ // If presets are defined in YAML, use them; otherwise, dynamically discover from repo
6992func GetPresets (language string ) ([]Preset , error ) {
7093 config , err := LoadTemplatesConfig ()
7194 if err != nil {
7295 return nil , err
7396 }
7497
75- presets , ok := config . Presets [ language ]
76- if ! ok {
77- return nil , fmt . Errorf ( "no presets for language: %s" , language )
98+ // If presets are defined in YAML, use them
99+ if presets , ok := config . Presets [ language ]; ok && len ( presets ) > 0 {
100+ return presets , nil
78101 }
79102
80- return presets , nil
103+ // Otherwise, dynamically discover templates from repository
104+ return discoverTemplates (config .Repo , language )
81105}
82106
83107// GetLanguages gets all supported languages
@@ -87,9 +111,109 @@ func GetLanguages() []string {
87111 return []string {}
88112 }
89113
90- languages := make ([]string , 0 , len (config .Presets ))
91- for lang := range config .Presets {
114+ // If presets are defined, use them
115+ if len (config .Presets ) > 0 {
116+ languages := make ([]string , 0 , len (config .Presets ))
117+ for lang := range config .Presets {
118+ languages = append (languages , lang )
119+ }
120+ return languages
121+ }
122+
123+ // Otherwise, use templates
124+ languages := make ([]string , 0 , len (config .Templates ))
125+ for lang := range config .Templates {
92126 languages = append (languages , lang )
93127 }
94128 return languages
95129}
130+
131+ // discoverTemplates dynamically discovers templates from the repository
132+ func discoverTemplates (repo , language string ) ([]Preset , error ) {
133+ // Create temporary directory
134+ tempDir , err := os .MkdirTemp ("" , "acontext-discover-*" )
135+ if err != nil {
136+ return nil , fmt .Errorf ("failed to create temp directory: %w" , err )
137+ }
138+ defer func () {
139+ _ = os .RemoveAll (tempDir )
140+ }()
141+
142+ // Sparse clone repository
143+ cmd := exec .Command (
144+ "git" , "clone" ,
145+ "--filter=blob:none" ,
146+ "--sparse" ,
147+ "--depth=1" ,
148+ "--quiet" ,
149+ repo ,
150+ tempDir ,
151+ )
152+ cmd .Stdout = io .Discard
153+ cmd .Stderr = io .Discard
154+ if err := cmd .Run (); err != nil {
155+ return nil , fmt .Errorf ("failed to clone repo: %w" , err )
156+ }
157+
158+ // Enable sparse-checkout for the language directory
159+ cmd = exec .Command ("git" , "sparse-checkout" , "init" , "--cone" )
160+ cmd .Dir = tempDir
161+ cmd .Stdout = io .Discard
162+ cmd .Stderr = io .Discard
163+ if err := cmd .Run (); err != nil {
164+ return nil , fmt .Errorf ("failed to init sparse-checkout: %w" , err )
165+ }
166+
167+ cmd = exec .Command ("git" , "sparse-checkout" , "set" , language )
168+ cmd .Dir = tempDir
169+ cmd .Stdout = io .Discard
170+ cmd .Stderr = io .Discard
171+ if err := cmd .Run (); err != nil {
172+ return nil , fmt .Errorf ("failed to set sparse-checkout: %w" , err )
173+ }
174+
175+ // List subdirectories in the language folder
176+ langDir := filepath .Join (tempDir , language )
177+ entries , err := os .ReadDir (langDir )
178+ if err != nil {
179+ return nil , fmt .Errorf ("failed to read language directory: %w" , err )
180+ }
181+
182+ var presets []Preset
183+ for _ , entry := range entries {
184+ if entry .IsDir () {
185+ templateName := entry .Name ()
186+ // Skip hidden directories and common non-template folders
187+ if strings .HasPrefix (templateName , "." ) || templateName == "node_modules" {
188+ continue
189+ }
190+
191+ presets = append (presets , Preset {
192+ Name : formatTemplateName (language , templateName ),
193+ Template : fmt .Sprintf ("%s.%s" , language , templateName ),
194+ })
195+ }
196+ }
197+
198+ if len (presets ) == 0 {
199+ return nil , fmt .Errorf ("no templates found for language: %s" , language )
200+ }
201+
202+ return presets , nil
203+ }
204+
205+ // formatTemplateName formats template name for display
206+ func formatTemplateName (language , templateName string ) string {
207+ // Capitalize first letter and replace hyphens/underscores with spaces
208+ parts := strings .FieldsFunc (templateName , func (r rune ) bool {
209+ return r == '-' || r == '_'
210+ })
211+
212+ for i , part := range parts {
213+ if len (part ) > 0 {
214+ parts [i ] = strings .ToUpper (part [:1 ]) + part [1 :]
215+ }
216+ }
217+
218+ return strings .Join (parts , " " )
219+ }
0 commit comments