@@ -2,6 +2,7 @@ package cmd
22
33import (
44 "encoding/json"
5+ "errors"
56 "fmt"
67 "os"
78 "path/filepath"
@@ -12,6 +13,15 @@ import (
1213 "codemap/scanner"
1314)
1415
16+ var errConfigExists = errors .New ("config already exists" )
17+
18+ type configInitResult struct {
19+ Path string
20+ TopExts []string
21+ TotalFiles int
22+ MatchedFiles int
23+ }
24+
1525// nonCodeExtensions are extensions excluded from "config init" auto-detection.
1626// These are documentation, data, or lock files that rarely represent the
1727// project's primary code.
@@ -46,106 +56,107 @@ func RunConfig(subCmd, root string) {
4656}
4757
4858func configInit (root string ) {
49- cfgPath := config .ConfigPath (root )
50-
51- // Warn if config already exists
52- if _ , err := os .Stat (cfgPath ); err == nil {
59+ result , err := initProjectConfig (root )
60+ if errors .Is (err , errConfigExists ) {
61+ cfgPath := config .ConfigPath (root )
5362 fmt .Fprintf (os .Stderr , "Config already exists: %s\n " , cfgPath )
5463 fmt .Fprintln (os .Stderr , "Use 'codemap config show' to view it, or edit directly." )
5564 os .Exit (1 )
5665 }
66+ if err != nil {
67+ fmt .Fprintf (os .Stderr , "Error creating config: %v\n " , err )
68+ os .Exit (1 )
69+ }
70+
71+ fmt .Printf ("Created %s\n " , result .Path )
72+ fmt .Println ()
73+ if len (result .TopExts ) == 0 {
74+ fmt .Println ("No code extensions detected — wrote empty config." )
75+ } else {
76+ fmt .Printf (" only: %s\n " , strings .Join (result .TopExts , ", " ))
77+ if result .TotalFiles > 0 {
78+ fmt .Printf (" (%d of %d files)\n " , result .MatchedFiles , result .TotalFiles )
79+ }
80+ }
81+ fmt .Println ()
82+ fmt .Println ("Edit the file to add 'exclude' patterns or adjust 'depth'." )
83+ }
84+
85+ func initProjectConfig (root string ) (configInitResult , error ) {
86+ cfgPath := config .ConfigPath (root )
87+ result := configInitResult {Path : cfgPath }
88+
89+ if _ , err := os .Stat (cfgPath ); err == nil {
90+ return result , errConfigExists
91+ } else if err != nil && ! os .IsNotExist (err ) {
92+ return result , err
93+ }
5794
58- // Scan the repo to find top extensions
5995 gitCache := scanner .NewGitIgnoreCache (root )
6096 files , err := scanner .ScanFiles (root , gitCache , nil , nil )
6197 if err != nil {
62- fmt .Fprintf (os .Stderr , "Error scanning files: %v\n " , err )
63- os .Exit (1 )
98+ return result , fmt .Errorf ("scan files: %w" , err )
6499 }
65100
66- // Count extensions
67101 extCount := make (map [string ]int )
68102 for _ , f := range files {
69- ext := strings .TrimPrefix (f .Ext , "." )
70- if ext == "" {
71- continue
72- }
73- ext = strings .ToLower (ext )
74- if nonCodeExtensions [ext ] {
103+ ext := strings .TrimPrefix (strings .ToLower (f .Ext ), "." )
104+ if ext == "" || nonCodeExtensions [ext ] {
75105 continue
76106 }
77107 extCount [ext ]++
78108 }
79109
80- // Sort by frequency
81110 type extEntry struct {
82111 Ext string
83112 Count int
84113 }
85114 var entries []extEntry
86115 for ext , count := range extCount {
87- entries = append (entries , extEntry {ext , count })
116+ entries = append (entries , extEntry {Ext : ext , Count : count })
88117 }
89118 sort .Slice (entries , func (i , j int ) bool {
90119 return entries [i ].Count > entries [j ].Count
91120 })
92121
93- // Take top 5
94- var topExts []string
95122 for i , e := range entries {
96123 if i >= 5 {
97124 break
98125 }
99- topExts = append (topExts , e .Ext )
126+ result . TopExts = append (result . TopExts , e .Ext )
100127 }
101128
102- if len (topExts ) == 0 {
103- fmt .Println ("No code extensions detected — writing empty config." )
104- topExts = nil
105- }
129+ cfg := config.ProjectConfig {Only : result .TopExts }
106130
107- cfg := config.ProjectConfig {
108- Only : topExts ,
109- }
110-
111- // Ensure .codemap/ directory exists
112131 if err := os .MkdirAll (filepath .Dir (cfgPath ), 0755 ); err != nil {
113- fmt .Fprintf (os .Stderr , "Error creating directory: %v\n " , err )
114- os .Exit (1 )
132+ return result , fmt .Errorf ("create .codemap directory: %w" , err )
115133 }
116134
117135 data , err := json .MarshalIndent (cfg , "" , " " )
118136 if err != nil {
119- fmt .Fprintf (os .Stderr , "Error encoding config: %v\n " , err )
120- os .Exit (1 )
137+ return result , fmt .Errorf ("encode config: %w" , err )
121138 }
122139 data = append (data , '\n' )
123140
124141 if err := os .WriteFile (cfgPath , data , 0644 ); err != nil {
125- fmt .Fprintf (os .Stderr , "Error writing config: %v\n " , err )
126- os .Exit (1 )
142+ return result , fmt .Errorf ("write config: %w" , err )
127143 }
128144
129- fmt .Printf ("Created %s\n " , cfgPath )
130- fmt .Println ()
131- fmt .Printf (" only: %s\n " , strings .Join (topExts , ", " ))
132- if len (files ) > 0 && len (topExts ) > 0 {
133- // Count how many files match the selected extensions
134- matchExts := make (map [string ]bool )
135- for _ , ext := range topExts {
145+ result .TotalFiles = len (files )
146+ if len (result .TopExts ) > 0 {
147+ matchExts := make (map [string ]bool , len (result .TopExts ))
148+ for _ , ext := range result .TopExts {
136149 matchExts [ext ] = true
137150 }
138- matched := 0
139151 for _ , f := range files {
140152 ext := strings .TrimPrefix (strings .ToLower (f .Ext ), "." )
141153 if matchExts [ext ] {
142- matched ++
154+ result . MatchedFiles ++
143155 }
144156 }
145- fmt .Printf (" (%d of %d files)\n " , matched , len (files ))
146157 }
147- fmt . Println ()
148- fmt . Println ( "Edit the file to add 'exclude' patterns or adjust 'depth'." )
158+
159+ return result , nil
149160}
150161
151162func configShow (root string ) {
0 commit comments