-
Notifications
You must be signed in to change notification settings - Fork 30
Expand file tree
/
Copy pathwrite.go
More file actions
336 lines (275 loc) · 8.36 KB
/
write.go
File metadata and controls
336 lines (275 loc) · 8.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package output
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
)
// WriteDataSources uses the packageName to determine whether to create a directory and package per data source.
// If packageName is an empty string, this indicates that the flag was not set, and the default behaviour is
// then to create a package and directory per data source. If packageName is set then all generated code is
// placed into the same directory and package.
func WriteDataSources(dataSourcesSchema, dataSourcesModels, customTypeValue, dataSourcesToFrom map[string][]byte, outputDir, packageName string) error {
for k, v := range dataSourcesSchema {
dirName := ""
if packageName == "" {
dirName = fmt.Sprintf("datasource_%s", k)
err := os.MkdirAll(filepath.Join(outputDir, dirName), os.ModePerm)
if err != nil {
return err
}
}
filename := fmt.Sprintf("%s_data_source_gen.go", k)
// Combine all content first
var allContent []byte
allContent = append(allContent, v...)
allContent = append(allContent, dataSourcesModels[k]...)
allContent = append(allContent, customTypeValue[k]...)
allContent = append(allContent, dataSourcesToFrom[k]...)
// Deduplicate the combined content
deduplicated, err := deduplicateGoCode(allContent)
if err != nil {
return err
}
f, err := os.Create(filepath.Join(outputDir, dirName, filename))
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(deduplicated)
if err != nil {
return err
}
}
return nil
}
// WriteResources uses the packageName to determine whether to create a directory and package per resource.
// If packageName is an empty string, this indicates that the flag was not set, and the default behaviour is
// then to create a package and directory per resource. If packageName is set then all generated code is
// placed into the same directory and package.
func WriteResources(resourcesSchema, resourcesModels, customTypeValue, resourcesToFrom map[string][]byte, outputDir, packageName string) error {
for k, v := range resourcesSchema {
dirName := ""
if packageName == "" {
dirName = fmt.Sprintf("resource_%s", k)
err := os.MkdirAll(filepath.Join(outputDir, dirName), os.ModePerm)
if err != nil {
return err
}
}
filename := fmt.Sprintf("%s_resource_gen.go", k)
// Combine all content first
var allContent []byte
allContent = append(allContent, v...)
allContent = append(allContent, resourcesModels[k]...)
allContent = append(allContent, customTypeValue[k]...)
allContent = append(allContent, resourcesToFrom[k]...)
// Deduplicate the combined content
deduplicated, err := deduplicateGoCode(allContent)
if err != nil {
return err
}
f, err := os.Create(filepath.Join(outputDir, dirName, filename))
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(deduplicated)
if err != nil {
return err
}
}
return nil
}
// WriteProviders uses the packageName to determine whether to create a directory and package for the provider.
// If packageName is an empty string, this indicates that the flag was not set, and the default behaviour is
// then to create a package and directory for the provider. If packageName is set then all generated code is
// placed into the same directory and package.
func WriteProviders(providersSchema, providerModels, customTypeValue, providerToFrom map[string][]byte, outputDir, packageName string) error {
for k, v := range providersSchema {
dirName := ""
if packageName == "" {
dirName = fmt.Sprintf("provider_%s", k)
err := os.MkdirAll(filepath.Join(outputDir, dirName), os.ModePerm)
if err != nil {
return err
}
}
filename := fmt.Sprintf("%s_provider_gen.go", k)
f, err := os.Create(filepath.Join(outputDir, dirName, filename))
if err != nil {
return err
}
_, err = f.Write(v)
if err != nil {
return err
}
_, err = f.Write(providerModels[k])
if err != nil {
return err
}
_, err = f.Write(customTypeValue[k])
if err != nil {
return err
}
_, err = f.Write(providerToFrom[k])
if err != nil {
return err
}
}
return nil
}
func WriteBytes(outputFilePath string, outputBytes []byte, forceOverwrite bool) error {
if _, err := os.Stat(outputFilePath); !errors.Is(err, fs.ErrNotExist) && !forceOverwrite {
return fmt.Errorf("file (%s) already exists and --force is false", outputFilePath)
}
f, err := os.Create(outputFilePath)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(outputBytes)
if err != nil {
return err
}
return nil
}
// deduplicateGoCode removes duplicate type and function declarations from Go source code
func deduplicateGoCode(content []byte) ([]byte, error) {
source := string(content)
lines := strings.Split(source, "\n")
// Track seen declarations
seen := make(map[string]bool)
result := make([]string, 0, len(lines))
i := 0
for i < len(lines) {
line := lines[i]
trimmedLine := strings.TrimSpace(line)
// Check for type declarations
if strings.HasPrefix(trimmedLine, "type ") {
// Extract type name
fields := strings.Fields(trimmedLine)
if len(fields) >= 2 {
typeName := fields[1]
key := "type:" + typeName
if seen[key] {
// Skip this entire type declaration
i = skipGoDeclaration(lines, i)
continue
} else {
seen[key] = true
}
}
}
// Check for function declarations
if strings.HasPrefix(trimmedLine, "func ") {
// Extract function name
funcName := extractFunctionName(trimmedLine)
if funcName != "" {
key := "func:" + funcName
if seen[key] {
// Skip this entire function declaration
i = skipGoDeclaration(lines, i)
continue
} else {
seen[key] = true
}
}
}
// Check for var declarations
if strings.HasPrefix(trimmedLine, "var _ ") {
// Extract the type being checked
parts := strings.Split(trimmedLine, "=")
if len(parts) > 1 {
rightPart := strings.TrimSpace(parts[1])
// Extract type name from "TypeName{}" pattern
if strings.HasSuffix(rightPart, "{}") {
typeName := strings.TrimSpace(strings.TrimSuffix(rightPart, "{}"))
key := "var:" + typeName
if seen[key] {
// Skip this var declaration
i++
continue
} else {
seen[key] = true
}
}
}
}
result = append(result, line)
i++
}
return []byte(strings.Join(result, "\n")), nil
}
// extractFunctionName extracts function name from a function declaration line
func extractFunctionName(line string) string {
// Handle both regular functions and methods
// func Name(...) or func (receiver) Name(...)
fields := strings.Fields(line)
if len(fields) < 2 {
return ""
}
if strings.HasPrefix(fields[1], "(") {
// Method with receiver: func (r Type) Name(...)
if len(fields) >= 4 {
// Extract receiver type and method name to create unique identifier
receiverPart := fields[2] // This should be the type name like "PrincipalType)"
funcName := fields[3]
// Clean up receiver type (remove closing parenthesis)
receiverType := strings.TrimSuffix(receiverPart, ")")
// Extract just the function name (remove parameters)
if idx := strings.Index(funcName, "("); idx > 0 {
funcName = funcName[:idx]
}
// Create unique key: ReceiverType.MethodName
return receiverType + "." + funcName
}
} else {
// Regular function: func Name(...)
funcName := fields[1]
if idx := strings.Index(funcName, "("); idx > 0 {
return funcName[:idx]
}
}
return ""
}
// skipGoDeclaration skips over a complete Go declaration (type, func, etc.)
func skipGoDeclaration(lines []string, start int) int {
if start >= len(lines) {
return start
}
line := strings.TrimSpace(lines[start])
// If it's a single-line declaration, just skip it
if !strings.Contains(line, "{") {
return start + 1
}
// For multi-line declarations, count braces to find the end
braceCount := 0
i := start
for i < len(lines) {
currentLine := lines[i]
// Count opening and closing braces
for _, char := range currentLine {
switch char {
case '{':
braceCount++
case '}':
braceCount--
}
}
i++
// If we've closed all braces, we're done
if braceCount == 0 {
break
}
}
// Skip any empty lines after the declaration
for i < len(lines) && strings.TrimSpace(lines[i]) == "" {
i++
}
return i
}