-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathparser.go
More file actions
152 lines (133 loc) · 4.52 KB
/
Copy pathparser.go
File metadata and controls
152 lines (133 loc) · 4.52 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
// Simple non-performant recursive descent parser for purposes of sqlcode; currently only
// supports the special @Enum declarations used by sqlcode. We only allow
// these on the top, and parsing will stop
// without any errors at the point hitting anything else.
package sqlparser
import (
"crypto/sha256"
"fmt"
"io/fs"
"path/filepath"
"regexp"
"slices"
"strings"
"github.com/vippsas/sqlcode/v2/sqlparser/mssql"
"github.com/vippsas/sqlcode/v2/sqlparser/pgsql"
"github.com/vippsas/sqlcode/v2/sqlparser/sqldocument"
)
var (
templateRoutineName string = "\ndeclare @RoutineName nvarchar(128)\nset @RoutineName = '%s'\n"
supportedSqlExtensions []string = []string{".sql", ".pgsql"}
// consider something a "sqlcode source file" if it contains [code]
// or a --sqlcode: header
isSqlCodeRegex = regexp.MustCompile(`^--sqlcode:|\[code\]`)
)
// Based on the input file extension, create the appropriate Document type
func NewDocumentFromExtension(extension string) sqldocument.Document {
switch extension {
case ".sql", "sql":
return &mssql.TSqlDocument{}
case ".pgsql", "pgsql":
return &pgsql.PGSqlDocument{}
default:
panic("unhandled document type: " + extension)
}
}
// Helper function
func ParseString(file, input string) sqldocument.Document {
doc := NewDocumentFromExtension(filepath.Ext(file))
doc.Parse([]byte(input), sqldocument.FileRef(file))
return doc
}
// ParseFileystems iterates through a list of filesystems and parses all supported
// SQL files and returns the combination of all of them.
//
// err will only return errors related to filesystems/reading. Errors
// related to parsing/sorting will be in result.Errors.
//
// ParseFilesystems will also sort create statements topologically.
func ParseFilesystems(fslst []fs.FS, includeTags []string) (filenames []string, result sqldocument.Document, err error) {
// We are being passed several *filesystems* here. It may be easy to pass in the same
// directory twice but that should not be encouraged, so if we get the same hash from
// two files, return an error. Only files containing [code] in some way will be
// considered here anyway
hashes := make(map[[32]byte]string)
if result == nil {
result = &mssql.TSqlDocument{}
}
for fidx, fsys := range fslst {
// WalkDir is in lexical order according to docs, so output should be stable
err = fs.WalkDir(fsys, ".",
func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
// Skip over any hidden directories; in particular .git
if strings.HasPrefix(path, ".") || strings.Contains(path, "/.") {
return nil
}
extension := filepath.Ext(path)
if !slices.Contains(supportedSqlExtensions, extension) {
return nil
}
buf, err := fs.ReadFile(fsys, path)
if err != nil {
return err
}
// Sniff whether the file is a SQLCode file or not. We can NOT use the parser
// for this, because the parser can be thrown off by errors, and we can't have
// a system where files are suddenly ignored when there are syntax errors!
// So using a more stable regex
if isSqlCodeRegex.Find(buf) != nil {
// protect against same file being referenced from 2 identical file systems..or just same file included twice
pathDesc := fmt.Sprintf("fs[%d]:%s", fidx, path)
hash := sha256.Sum256(buf)
existingPathDesc, hashExists := hashes[hash]
if hashExists {
return fmt.Errorf("file %s has exact same contents as %s (possibly in different filesystems)",
pathDesc, existingPathDesc)
}
hashes[hash] = pathDesc
fdoc := NewDocumentFromExtension(extension)
err = fdoc.Parse(buf, sqldocument.FileRef(path))
if err != nil {
return fmt.Errorf("error parsing file %s: %w", pathDesc, err)
}
// only include if include tags match
if matchesIncludeTags(fdoc.PragmaIncludeIf(), includeTags) {
filenames = append(filenames, pathDesc)
result.Include(fdoc)
}
}
return nil
})
if err != nil {
return
}
}
result.Sort()
return
}
func matchesIncludeTags(required []string, got []string) bool {
for _, r := range required {
found := false
for _, g := range got {
if g == r {
found = true
break
}
}
if !found {
return false
}
}
return true
}
func IsSqlcodeConstVariable(varname string) bool {
return strings.HasPrefix(varname, "@Enum") ||
strings.HasPrefix(varname, "@ENUM_") ||
strings.HasPrefix(varname, "@enum_") ||
strings.HasPrefix(varname, "@Const") ||
strings.HasPrefix(varname, "@CONST_") ||
strings.HasPrefix(varname, "@const_")
}