-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathincludes.go
223 lines (187 loc) · 5.48 KB
/
includes.go
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
package main
import (
"bufio"
"errors"
"fmt"
"os"
"path"
"path/filepath"
"regexp"
"strings"
)
// Include represent the include file
type Include struct {
path string
// options []Option
}
// ErrorIncludeLoop happens in case of an infinite loop between included files
var ErrorIncludeLoop = errors.New("include loop")
// ErrorEmptyPath happens when a path is an empty string
var ErrorEmptyPath = errors.New("empty path")
// ErrorIncludeOutOfChroot happens when an include is not in the inside the agnosticV repo.
var ErrorIncludeOutOfChroot = errors.New("include path is out of chroot")
func containsPath(l []Include, p string) bool {
for _, a := range l {
if a.path == p {
return true
}
}
return false
}
// getMetaPath builds the path of the meta by prepending '.meta' to
// the original extension of the included file.
//
// dev.yaml => dev.meta.yaml
// dev.yml => dev.meta.yml
func getMetaPath(path string) (string, error) {
if path == "" {
return "", ErrorEmptyPath
}
extension := filepath.Ext(path)
meta := strings.TrimSuffix(path, extension) + ".meta"
// Detect which extension to use based on file existence
if fileExists(meta + ".yml") {
return meta + ".yml", nil
}
if fileExists(meta + ".yaml") {
return meta + ".yaml", nil
}
// Return same extension as file
return meta + extension, nil
}
func isMetaPath(path string) bool {
ext := filepath.Ext(path)
if ext == ".yml" || ext == ".yaml" {
if filepath.Ext(strings.TrimSuffix(path, ext)) == ".meta" {
return true
}
}
return false
}
// function getMergeList return the merge list for a catalog items
// merge list contains: common files and includes.
func getMergeList(path string) ([]Include, error) {
result := []Include{}
done := map[string]bool{}
for previous, next := "", path; next != "" && next != previous; next = nextCommonFile(next) {
allIncludes, innerDone, err := parseAllIncludes(next, done)
done = innerDone
if err != nil {
logErr.Println("Error loading includes for", next)
return result, err
}
result = append([]Include{{path: next}}, result...)
result = append(allIncludes, result...)
previous = next
}
return result, nil
}
func printPaths(mergeList []Include, workdir string) {
if len(mergeList) > 0 {
fmt.Println("# MERGED:")
}
for i := 0; i < len(mergeList); i = i + 1 {
if relativePath, err := filepath.Rel(workdir, mergeList[i].path); err == nil && len(relativePath) < len(mergeList[i].path) {
fmt.Printf("# %s\n", relativePath)
} else {
fmt.Printf("# %s\n", mergeList[i].path)
}
}
}
var regexInclude = regexp.MustCompile(`^[ \t]*#include[ \t]+("(.*?[^\\])"|([^ \t]+))[ \t]*$`)
// parseInclude function parses the includes in a line
func parseInclude(line string) (bool, Include) {
result := regexInclude.FindAllStringSubmatch(line, -1)
if len(result) == 0 {
return false, Include{}
}
if len(result) > 1 {
logErr.Println("Could not parse include line:", line)
return false, Include{}
}
if len(result[0]) < 4 {
logErr.Println("Could not parse include line:", line)
return false, Include{}
}
if result[0][2] == "" {
if result[0][3] == "" {
return false, Include{}
}
return true, Include{
path: result[0][3],
}
}
return true, Include{
path: result[0][2],
}
}
// parseAllIncludes parses all includes in a file
func parseAllIncludes(path string, done map[string]bool) ([]Include, map[string]bool, error) {
logDebug.Println("parseAllIncludes(", path, done, ")")
if !fileExists(path) {
logErr.Println(path, "path does not exist")
return []Include{}, done, errors.New("path include does not exist")
}
if val, ok := done[path]; ok && val {
logErr.Println(path, "is included more than once")
return []Include{}, done, ErrorIncludeLoop
}
done[path] = true
result := []Include{}
// Check if path has a meta file
if meta, err := getMetaPath(path); err == nil && fileExists(meta) {
innerIncludes, innerDone, err := parseAllIncludes(meta, done)
done = innerDone
if err != nil {
return []Include{}, done, err
}
innerIncludes = append(innerIncludes, Include{path: meta})
result = append(innerIncludes, result...)
}
file, err := os.Open(path)
if err != nil {
return []Include{}, done, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if ok, include := parseInclude(line); ok {
logDebug.Println("parseInclude(", line, ")")
include.path, err = resolvePath(rootFlag, include.path, path)
if err != nil {
return []Include{}, done, err
}
var innerIncludes []Include
var innerDone map[string]bool
if isCatalogItem(rootFlag, include.path) {
innerIncludes, err = getMergeList(include.path)
// Remove last element, which is the current file
innerIncludes = innerIncludes[:len(innerIncludes)-1]
if err != nil {
return []Include{}, done, err
}
} else {
innerIncludes, innerDone, err = parseAllIncludes(include.path, done)
done = innerDone
if err != nil {
return []Include{}, done, err
}
}
innerIncludes = append(innerIncludes, include)
result = append(result, innerIncludes...)
}
}
return result, done, nil
}
// resolvePath return the absolute path, with context
func resolvePath(root string, includePath string, contextFile string) (string, error) {
if includePath[0] == '/' {
return filepath.Join(root, filepath.Clean(includePath)), nil
}
result := filepath.Join(path.Dir(contextFile), filepath.Clean(includePath))
if !isRoot(root, result) {
return "", ErrorIncludeOutOfChroot
}
return result, nil
}