@@ -15,9 +15,9 @@ import (
15
15
var ignore gitignore.GitIgnore
16
16
17
17
type Config struct {
18
- // one of available encodings from https://github.com/pkoukk/tiktoken-go?tab=readme-ov-file#available-encodings
19
- Model string `flag:"model " envvar:"MODEL " default:"gpt-4"` // "The model to use for tokenization."
20
- OutputFile string `flag:"output " envvar:"FILE " default:"file-for-ai.txt "`
18
+ Model string `flag:"model" envvar:"MODEL" default:"gpt-4"`
19
+ OutputFile string `flag:"output " envvar:"FILE " default:"file- for-ai.txt"`
20
+ IgnoreGitIgnore bool `flag:"ignore-gitignore " envvar:"IGNORE_GITIGNORE " default:"false "`
21
21
}
22
22
23
23
func main () {
@@ -28,30 +28,21 @@ func main() {
28
28
panic (err )
29
29
}
30
30
31
- // Check if at least a directory path is provided
32
31
if len (os .Args ) < 2 {
33
- fmt .Println ("Error: Directory path is required." )
34
- fmt .Println ("Usage: file-for-ai <directory> [output file]" )
32
+ fmt .Println ("Error: Directory path or glob pattern is required." )
33
+ fmt .Println ("Usage: file-for-ai <directory|pattern > [output file]" )
35
34
os .Exit (1 )
36
35
}
37
36
38
- directoryPath := os .Args [1 ]
37
+ inputPath := os .Args [1 ]
39
38
40
39
outputFileName := conf .OutputFile
41
40
42
- // Backup the output file if it already exists
43
41
if _ , err := os .Stat (outputFileName ); ! os .IsNotExist (err ) {
44
42
fmt .Printf ("Output file %s already exists\n " , outputFileName )
45
43
os .Exit (1 )
46
44
}
47
45
48
- //gitIgnorePath := filepath.Join(directoryPath, ".gitignore")
49
- ignore , err = gitignore .NewRepository (directoryPath )
50
- if err != nil {
51
- fmt .Println ("Error creating output file:" , err )
52
- os .Exit (1 )
53
- }
54
-
55
46
outputFile , err := os .Create (outputFileName )
56
47
if err != nil {
57
48
fmt .Println ("Error creating output file:" , err )
@@ -69,57 +60,96 @@ func main() {
69
60
tokens := 0
70
61
71
62
fmt .Println ("Merging files:" )
72
- err = filepath .Walk (directoryPath , func (path string , info os.FileInfo , err error ) error {
73
- if info .Name () == outputFileName {
74
- return nil
75
- }
76
63
77
- if err != nil {
78
- fmt .Println ("Error accessing path:" , path , err )
79
- return err
80
- }
64
+ if isDirectory (inputPath ) {
81
65
82
- relativePath , err := filepath .Rel (directoryPath , path )
83
- if err != nil {
84
- fmt .Println ("Error processing relative path:" , path , err )
85
- return err
66
+ if ! conf .IgnoreGitIgnore {
67
+ fmt .Print ("Filtering files using .gitignore... " )
68
+ ignore , err = gitignore .NewRepository (inputPath )
69
+ if err != nil {
70
+ fmt .Println ("Error creating gitignore repository:" , err )
71
+ os .Exit (1 )
72
+ }
86
73
}
87
74
88
- if ! info .IsDir () && ! isGitIgnored (relativePath , info .IsDir ()) && isTextFile (path ) && ! strings .HasPrefix (path , "." ) {
89
- fileContents , err := os .ReadFile (path )
75
+ err = filepath .Walk (inputPath , func (path string , info os.FileInfo , err error ) error {
90
76
if err != nil {
91
- fmt .Println ("Error reading file :" , path , err )
77
+ fmt .Println ("Error accessing path :" , path , err )
92
78
return err
93
79
}
94
- fmt .Println (relativePath )
95
- tokens += len (tkm .Encode (string (fileContents ), nil , nil ))
80
+ return processFile (inputPath , path , info , outputFile , tkm , & tokens , outputFileName )
81
+ })
82
+ } else {
83
+ files , err := filepath .Glob (inputPath )
84
+ if err != nil {
85
+ fmt .Println ("Error parsing glob pattern:" , err )
86
+ os .Exit (1 )
87
+ }
96
88
97
- separator := fmt .Sprintf ("\n \n >>>>>> %s <<<<<<\n \n " , relativePath )
98
- if _ , err := outputFile .WriteString (separator ); err != nil {
99
- fmt .Println ("Error writing separator to output file:" , err )
100
- return err
89
+ for _ , path := range files {
90
+ info , err := os .Stat (path )
91
+ if err != nil {
92
+ fmt .Println ("Error accessing file:" , path , err )
93
+ continue
101
94
}
102
-
103
- if _ , err := outputFile . Write ( fileContents ); err != nil {
104
- fmt .Println ("Error writing file contents to output file:" , err )
105
- return err
95
+ err = processFile ( filepath . Dir ( path ), path , info , outputFile , tkm , & tokens , outputFileName )
96
+ if err != nil {
97
+ fmt .Println ("Error processing file:" , path , err )
98
+ continue
106
99
}
107
100
}
108
-
109
- return nil
110
- })
101
+ }
111
102
112
103
fmt .Println ()
113
104
if err != nil {
114
- fmt .Println ("Error walking through the directory:" , err )
105
+ fmt .Println ("Error walking through the directory or processing pattern :" , err )
115
106
return
116
107
}
117
108
118
109
fmt .Printf ("Files merged successfully into %s\n " , outputFileName )
119
110
fmt .Printf ("Total tokens for model %s: %s\n " , conf .Model , formatIntNumber (tokens ))
120
111
}
121
112
122
- // formats int by adding spaces between thousands
113
+ func isDirectory (path string ) bool {
114
+ info , err := os .Stat (path )
115
+ return err == nil && info .IsDir ()
116
+ }
117
+
118
+ func processFile (basePath , path string , info os.FileInfo , outputFile * os.File , tkm * tiktoken.Tiktoken , tokens * int , outputFileName string ) error {
119
+ if info .Name () == outputFileName {
120
+ return nil
121
+ }
122
+
123
+ relativePath , err := filepath .Rel (basePath , path )
124
+ if err != nil {
125
+ fmt .Println ("Error processing relative path:" , path , err )
126
+ return err
127
+ }
128
+
129
+ if ! info .IsDir () && ! isGitIgnored (relativePath , info .IsDir ()) && isTextFile (path ) && ! strings .HasPrefix (path , "." ) {
130
+ fileContents , err := os .ReadFile (path )
131
+ if err != nil {
132
+ fmt .Println ("Error reading file:" , path , err )
133
+ return err
134
+ }
135
+ fmt .Println (relativePath )
136
+ * tokens += len (tkm .Encode (string (fileContents ), nil , nil ))
137
+
138
+ separator := fmt .Sprintf ("\n \n >>>>>> %s <<<<<<\n \n " , relativePath )
139
+ if _ , err := outputFile .WriteString (separator ); err != nil {
140
+ fmt .Println ("Error writing separator to output file:" , err )
141
+ return err
142
+ }
143
+
144
+ if _ , err := outputFile .Write (fileContents ); err != nil {
145
+ fmt .Println ("Error writing file contents to output file:" , err )
146
+ return err
147
+ }
148
+ }
149
+
150
+ return nil
151
+ }
152
+
123
153
func formatIntNumber (n int ) string {
124
154
s := fmt .Sprintf ("%d" , n )
125
155
if len (s ) <= 3 {
@@ -148,7 +178,6 @@ func isGitIgnored(path string, isDir bool) bool {
148
178
return false
149
179
}
150
180
151
- // isTextFile checks the file extension against a list of known non-text file extensions.
152
181
func isTextFile (path string ) bool {
153
182
ext := strings .ToLower (filepath .Ext (path ))
154
183
return ! nonTextFileExtensions [ext ]
0 commit comments