-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathreader.go
148 lines (130 loc) · 3.83 KB
/
reader.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
package sequelie
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"os"
"strings"
"sync"
)
type iReader struct{}
var reader = iReader{}
// CACHING BYTES
// These are to cache the following strings to byte conversions.
// It may be redundant, but still better to have.
var prefixBytes = []byte("-- sequelie:")
var prefixBytesLen = len(prefixBytes)
var commentBytes = []byte("--")
var declareBytes = []byte("declare")
var queryBytes = []byte("query")
var namespaceBytes = []byte("namespace")
var enableBytes = []byte("enable")
var operatorsBytes = []byte("operators")
var separatorBytes = []byte(".")
var newLineBytes = []byte("\n")
var declarationPrefixBytes = []byte("{$$")
var encloserBytes = []byte{'}'}
var insertPrefixBytes = []byte("{$INSERT:")
var mutex = sync.RWMutex{}
type localOptions struct {
Operators bool
}
func (reader *iReader) scan(f io.Reader, m map[string]*Query, options *Options) error {
buf := bufio.NewScanner(f)
var builder strings.Builder
var namespace, address *[]byte
var declarations [][][]byte
local := localOptions{Operators: false}
var push = func() error {
if address != nil {
mutex.Lock()
m[string(*address)] = ptr(Query(strings.TrimSpace(builder.String())))
mutex.Unlock()
builder.Reset()
local = localOptions{Operators: false}
return nil
}
return errors.New("[no_address] file %s has a query with no address")
}
for buf.Scan() {
line := buf.Bytes()
if !insensitiveHasPrefix(line, prefixBytes) {
// FUN FACT: This if-statement exists to prevent white spaces while address is null.
if address != nil {
if bytes.HasPrefix(line, commentBytes) && !options.AllowComments {
continue
} else {
for _, declaration := range declarations {
line = bytes.ReplaceAll(line, declaration[0], declaration[1])
}
if local.Operators && namespace != nil {
clauses := bytes.Fields(line)
for _, clause := range clauses {
if bytes.HasPrefix(clause, insertPrefixBytes) && bytes.HasSuffix(clause, encloserBytes) {
name := clause[len(insertPrefixBytes):]
name = name[:len(name)-1]
mutex.RLock()
v, e := m[string(name)]
mutex.RUnlock()
if e {
line = bytes.Replace(line, clause, v.Bytes(), 1)
} else {
options.Logger.Println(
"ERR sequelie couldn't insert ", string(name), " into ", string(*address),
", it is likely because the former hasn't been initialized.")
}
}
}
}
}
builder.Write(newLineBytes)
builder.Write(line)
}
continue
}
args := bytes.Fields(line[prefixBytesLen:])
if len(args) > 1 {
if bytes.EqualFold(args[0], namespaceBytes) {
namespace = ptr(bytes.ToLower(args[1]))
} else if bytes.EqualFold(args[0], queryBytes) {
if namespace == nil {
return errors.New("[no_namespace] file %s has no namespace")
}
if address != nil {
if err := push(); err != nil {
return err
}
}
address = ptr(bytes.Join([][]byte{*namespace, bytes.ToLower(args[1])}, separatorBytes))
} else if bytes.EqualFold(args[0], declareBytes) {
declarations = append(declarations, [][]byte{
bytes.Join([][]byte{declarationPrefixBytes, encloserBytes}, args[1]),
line[(prefixBytesLen + 1 + len(args[0]) + 1 + len(args[1])):],
})
} else if bytes.EqualFold(args[0], enableBytes) {
if bytes.EqualFold(args[1], operatorsBytes) {
local.Operators = true
}
}
}
}
return push()
}
func (reader *iReader) read(file string, m map[string]*Query, options *Options) error {
f, err := os.Open(file)
if err != nil {
return err
}
defer func(f *os.File) {
err := f.Close()
if err != nil {
options.Logger.Println("ERR sequelie failed to close file ", file, ": ", err)
}
}(f)
if err = reader.scan(f, m, options); err != nil {
return errors.New(fmt.Sprintf(err.Error(), file))
}
return nil
}