170 lines
3.5 KiB
Go
170 lines
3.5 KiB
Go
package nginx
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// ErrNullReceiver is raised when a null receiver is provided
|
|
var ErrNullReceiver = fmt.Errorf("receiver must not be null")
|
|
|
|
// ErrInvalidReceiver is raised when an invalid receiver is provided
|
|
var ErrInvalidReceiver = fmt.Errorf("receiver must be compatible with *[]*Line")
|
|
|
|
// ErrUnexpectedSectionClose is raised when reading a section close '}' without an
|
|
// open section
|
|
var ErrUnexpectedSectionClose = fmt.Errorf("unexpected section close")
|
|
|
|
// ErrUnclosedSection is raised when there is unclosed sections reaching the end
|
|
// of the file
|
|
var ErrUnclosedSection = fmt.Errorf("unclosed section")
|
|
|
|
// ErrInvalidSyntax is raised when a line cannot be understood
|
|
var ErrInvalidSyntax = fmt.Errorf("invalid syntax")
|
|
|
|
// decoder implements parser.Decoder
|
|
type decoder struct{ reader io.Reader }
|
|
|
|
func (d *decoder) Decode(v interface{}) error {
|
|
|
|
// check 'v'
|
|
if v == nil {
|
|
return ErrNullReceiver
|
|
}
|
|
vcast, ok := v.(*nginx)
|
|
if !ok {
|
|
return ErrInvalidReceiver
|
|
}
|
|
vcast.Lines = make([]*Line, 0)
|
|
|
|
r := bufio.NewReader(d.reader)
|
|
n := -1 // line number
|
|
|
|
// regexes
|
|
reSection := regexp.MustCompile(`(?m)^([a-z0-9_-]+)\s*\{$`)
|
|
reAssign := regexp.MustCompile(`(?m)^([a-z0-9_-]+)\s+([^;#]+);$`)
|
|
reInclude := regexp.MustCompile(`(?m)^include\s+([^;#]+);$`)
|
|
|
|
// actual section stack
|
|
stack := make([]*Line, 0)
|
|
|
|
for {
|
|
n++
|
|
|
|
l := &Line{Type: NONE}
|
|
|
|
// 1. read line
|
|
notrim, err := r.ReadString('\n')
|
|
if err == io.EOF {
|
|
break
|
|
} else if err != nil {
|
|
return &LineError{n, err}
|
|
}
|
|
notrim = strings.Trim(notrim, "\r\n")
|
|
|
|
// 2. ignore empty
|
|
line := strings.Trim(notrim, " \t")
|
|
if len(line) < 1 {
|
|
continue
|
|
}
|
|
|
|
// 2. comment
|
|
if line[0] == '#' {
|
|
l.Type = COMMENT
|
|
l.Components = []string{strings.Trim(line[1:], " \t")}
|
|
} else if line[0] == ';' {
|
|
l.Type = COLONCOMMENT
|
|
l.Components = []string{strings.Trim(line[1:], " \t")}
|
|
} else if line[0] == '}' {
|
|
l.Type = SECTIONEND
|
|
if len(stack) < 1 {
|
|
return &LineError{n, ErrUnexpectedSectionClose}
|
|
}
|
|
}
|
|
|
|
// 3. section
|
|
if l.Type == NONE {
|
|
match := reSection.FindStringSubmatch(line)
|
|
if match != nil {
|
|
l.Type = SECTION
|
|
l.Components = match[1:]
|
|
l.Lines = make([]*Line, 0)
|
|
stack = append(stack, l)
|
|
}
|
|
}
|
|
|
|
// 4. include
|
|
if l.Type == NONE {
|
|
match := reInclude.FindStringSubmatch(line)
|
|
if match != nil {
|
|
l.Type = INCLUDE
|
|
l.Components = []string{match[1]}
|
|
}
|
|
}
|
|
|
|
// 5. assignment
|
|
if l.Type == NONE {
|
|
match := reAssign.FindStringSubmatch(line)
|
|
if match != nil {
|
|
l.Type = ASSIGNMENT
|
|
l.Components = match[1:]
|
|
}
|
|
}
|
|
|
|
// 6. invalid type
|
|
if l.Type == NONE {
|
|
return &LineError{n, ErrInvalidSyntax}
|
|
}
|
|
|
|
// 7. pop section
|
|
if l.Type == SECTIONEND {
|
|
cur := stack[len(stack)-1] // pop
|
|
stack = stack[0 : len(stack)-1] // pop
|
|
|
|
var last []*Line
|
|
if len(stack) > 0 {
|
|
last = stack[len(stack)-1].Lines
|
|
} else {
|
|
last = vcast.Lines
|
|
}
|
|
last = append(last, cur)
|
|
|
|
continue
|
|
}
|
|
|
|
// 8. add to section / or receiver
|
|
stacklen := len(stack)
|
|
if stacklen > 0 && l.Type == SECTION {
|
|
stacklen--
|
|
}
|
|
|
|
if stacklen > 0 {
|
|
tail := stack[stacklen-1]
|
|
tail.Lines = append(tail.Lines, l)
|
|
} else {
|
|
vcast.Lines = append(vcast.Lines, l)
|
|
}
|
|
|
|
}
|
|
|
|
// fail on unclosed section
|
|
if len(stack) > 0 {
|
|
return ErrUnclosedSection
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
func inArray(haystack string, needle byte) bool {
|
|
for i, l := 0, len(haystack); i < l; i++ {
|
|
if haystack[i] == needle {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|