forked from mitchellh/go-libucl
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathparser.go
205 lines (175 loc) · 5.2 KB
/
parser.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
package libucl
import (
"errors"
"os"
"sync"
"unsafe"
)
// #include "go-libucl.h"
import "C"
// MacroFunc is the callback type for macros.
type MacroFunc func(string)
// ParserFlag are flags that can be used to initialize a parser.
type ParserFlag int
const (
// ParserKeyLowercase will lowercase all keys.
ParserKeyLowercase ParserFlag = C.UCL_PARSER_KEY_LOWERCASE
// ParserZeroCopy will attempt to do a zero-copy parse if possible.
ParserZeroCopy ParserFlag = C.UCL_PARSER_ZEROCOPY
// ParserNoTime will treat time values as strings.
ParserNoTime ParserFlag = C.UCL_PARSER_NO_TIME
// ParserNoImplicitArrays forces the creation explicit arrays instead of
// implicit ones
ParserNoImplicitArrays ParserFlag = C.UCL_PARSER_NO_IMPLICIT_ARRAYS
)
// Keeps track of all the macros internally
var macros map[int]MacroFunc
var macrosIdx int
var macrosLock sync.Mutex
// Parser is responsible for parsing libucl data.
type Parser struct {
macros []int
parser *C.struct_ucl_parser
}
// ParseString parses a string and returns the top-level object.
func ParseString(data string) (*Object, error) {
p := NewParser(0)
defer p.Close()
if err := p.AddString(data); err != nil {
return nil, err
}
return p.Object(), nil
}
// NewParser returns a parser
func NewParser(flags ParserFlag) *Parser {
return &Parser{
parser: C.ucl_parser_new(C.int(flags)),
}
}
// AddString adds a string data to parse.
func (p *Parser) AddString(data string) error {
cs := C.CString(data)
defer C.free(unsafe.Pointer(cs))
result := C.ucl_parser_add_string(p.parser, cs, C.size_t(len(data)))
if !result {
errstr := C.ucl_parser_get_error(p.parser)
return errors.New(C.GoString(errstr))
}
return nil
}
// AddFile adds a file to parse.
func (p *Parser) AddFile(path string) error {
cs := C.CString(path)
defer C.free(unsafe.Pointer(cs))
result := C.ucl_parser_add_file(p.parser, cs)
if !result {
errstr := C.ucl_parser_get_error(p.parser)
return errors.New(C.GoString(errstr))
}
return nil
}
// Close frees the parser. Once it is freed it can no longer be used. You
// should always free the parser once you're done with it to clean up
// any unused memory.
func (p *Parser) Close() {
C.ucl_parser_free(p.parser)
if len(p.macros) > 0 {
macrosLock.Lock()
defer macrosLock.Unlock()
for _, idx := range p.macros {
delete(macros, idx)
}
}
}
// Object retrieves the root-level object for a configuration.
func (p *Parser) Object() *Object {
obj := C.ucl_parser_get_object(p.parser)
if obj == nil {
return nil
}
return &Object{object: obj}
}
// RegisterMacro registers a macro that is called from the configuration.
func (p *Parser) RegisterMacro(name string, f MacroFunc) {
// Register it globally
macrosLock.Lock()
if macros == nil {
macros = make(map[int]MacroFunc)
}
for macros[macrosIdx] != nil {
macrosIdx++
}
idx := macrosIdx
macros[idx] = f
macrosIdx++
macrosLock.Unlock()
// Register the index with our parser so we can free it
p.macros = append(p.macros, idx)
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
C.ucl_parser_register_macro(
p.parser,
cname,
C._go_macro_handler_func(),
C._go_macro_index(C.int(idx)))
}
//export go_macro_call
func go_macro_call(id C.int, data *C.char, n C.int) C.bool {
macrosLock.Lock()
f := macros[int(id)]
macrosLock.Unlock()
// Macro not found, return error
if f == nil {
return false
}
// Macro found, call it!
f(C.GoStringN(data, n))
return true
}
// SetFileVariables sets the standard file variables ($FILENAME and $CURDIR) based
// on the provided filepath. If the argument expand is true, the path will be expanded
// out to an absolute path
//
// For example, if the current directory is /etc/nginx, and you give a path of
// ../file.conf, with exand = false, $FILENAME = ../file.conf and $CURDIR = ..,
// while with expand = true, $FILENAME = /etc/file.conf and $CURDIR = /etc
func (p *Parser) SetFileVariables(filepath string, expand bool) error {
cpath := C.CString(filepath)
defer C.free(unsafe.Pointer(cpath))
result := C.ucl_parser_set_filevars(p.parser, cpath, C.bool(expand))
if !result {
errstr := C.ucl_parser_get_error(p.parser)
return errors.New(C.GoString(errstr))
}
return nil
}
// RegisterVariable adds a new variable to the parser, which can be accessed in
// the configuration file as $variable_name
func (p *Parser) RegisterVariable(variable, value string) {
cVariable := C.CString(variable)
defer C.free(unsafe.Pointer(cVariable))
cValue := C.CString(value)
defer C.free(unsafe.Pointer(cValue))
C.ucl_parser_register_variable(p.parser, cVariable, cValue)
}
// AddFileAndSetVariables is a combination of AddFile and SetFileVariables.
// It is meant to be a simple way to do both actions in a single function call.
func (p *Parser) AddFileAndSetVariables(path string, expand bool) error {
err := p.AddFile(path)
if err != nil {
return err
}
err = p.SetFileVariables(path, expand)
return err
}
// AddOpenFile reads in the configuration from a file already opened using os.Open
// or a related function.
func (p *Parser) AddOpenFile(f *os.File) error {
fd := f.Fd()
result := C.ucl_parser_add_fd(p.parser, C.int(fd))
if !result {
errstr := C.ucl_parser_get_error(p.parser)
return errors.New(C.GoString(errstr))
}
return nil
}