update nginx/decoder (working for simplest case) | add tests for all line types + nested sections
This commit is contained in:
parent
08b8cf8493
commit
194b5647ee
|
@ -8,8 +8,11 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrNullReceiver = fmt.Errorf("decoder receiver must not be null")
|
var ErrNullReceiver = fmt.Errorf("receiver must not be null")
|
||||||
var ErrInvalidReceiver = fmt.Errorf("decoder receiver must be compatible with *[]*Line")
|
var ErrInvalidReceiver = fmt.Errorf("receiver must be compatible with *[]*Line")
|
||||||
|
var ErrUnexpectedSectionClose = fmt.Errorf("unexpected section close")
|
||||||
|
var ErrUnclosedSection = fmt.Errorf("unclosed section")
|
||||||
|
var ErrInvalidSyntax = fmt.Errorf("invalid syntax")
|
||||||
|
|
||||||
// decoder implements parser.Decoder
|
// decoder implements parser.Decoder
|
||||||
type decoder struct{ reader io.Reader }
|
type decoder struct{ reader io.Reader }
|
||||||
|
@ -20,21 +23,22 @@ func (d *decoder) Decode(v interface{}) error {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return ErrNullReceiver
|
return ErrNullReceiver
|
||||||
}
|
}
|
||||||
vcast, ok := v.(*[]*Line)
|
vcast, ok := v.(*nginx)
|
||||||
if !ok {
|
if !ok {
|
||||||
return ErrInvalidReceiver
|
return ErrInvalidReceiver
|
||||||
}
|
}
|
||||||
|
vcast.Lines = make([]*Line, 0)
|
||||||
|
|
||||||
r := bufio.NewReader(d.reader)
|
r := bufio.NewReader(d.reader)
|
||||||
n := -1 // line number
|
n := -1 // line number
|
||||||
|
|
||||||
// regexes
|
// regexes
|
||||||
reSection := regexp.MustCompile(`(?m)^([a-z0-9_-]+)\s+\{$`)
|
reSection := regexp.MustCompile(`(?m)^([a-z0-9_-]+)\s*\{$`)
|
||||||
reAssign := regexp.MustCompile(`(?m)^([a-z0-9_-]+)\s+([^;#]+);$`)
|
reAssign := regexp.MustCompile(`(?m)^([a-z0-9_-]+)\s+([^;#]+);$`)
|
||||||
reInclude := regexp.MustCompile(`(?m)^include\s+([^;#]+);$`)
|
reInclude := regexp.MustCompile(`(?m)^include\s+([^;#]+);$`)
|
||||||
|
|
||||||
// actual section tree
|
// actual section stack
|
||||||
// tree := []Line{}
|
stack := make([]*Line, 0)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
n++
|
n++
|
||||||
|
@ -46,18 +50,19 @@ func (d *decoder) Decode(v interface{}) error {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return err
|
return &LineError{n, err}
|
||||||
}
|
}
|
||||||
|
notrim = strings.Trim(notrim, "\r\n")
|
||||||
|
|
||||||
// 2. ignore empty
|
// 2. ignore empty
|
||||||
line := strings.Trim(notrim, " \t\r\n")
|
line := strings.Trim(notrim, " \t")
|
||||||
if len(line) < 1 {
|
if len(line) < 1 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. get indentation
|
// 3. get indentation
|
||||||
firstChar := 0
|
var firstChar int
|
||||||
for ; !in_array(" \t\r\n", notrim[firstChar:][0]); firstChar++ {
|
for firstChar = 0; in_array(" \t", notrim[firstChar]); firstChar++ {
|
||||||
}
|
}
|
||||||
l.Indent = notrim[0:firstChar]
|
l.Indent = notrim[0:firstChar]
|
||||||
|
|
||||||
|
@ -68,6 +73,11 @@ func (d *decoder) Decode(v interface{}) error {
|
||||||
} else if line[0] == ';' {
|
} else if line[0] == ';' {
|
||||||
l.Type = COLONCOMMENT
|
l.Type = COLONCOMMENT
|
||||||
l.Components = []string{strings.Trim(line[1:], " \t")}
|
l.Components = []string{strings.Trim(line[1:], " \t")}
|
||||||
|
} else if line[0] == '}' {
|
||||||
|
l.Type = SECTIONEND
|
||||||
|
if len(stack) < 1 {
|
||||||
|
return &LineError{n, ErrUnexpectedSectionClose}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. section
|
// 4. section
|
||||||
|
@ -76,6 +86,8 @@ func (d *decoder) Decode(v interface{}) error {
|
||||||
if match != nil {
|
if match != nil {
|
||||||
l.Type = SECTION
|
l.Type = SECTION
|
||||||
l.Components = match[1:]
|
l.Components = match[1:]
|
||||||
|
l.Lines = make([]*Line, 0)
|
||||||
|
stack = append(stack, l)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,8 +109,45 @@ func (d *decoder) Decode(v interface{}) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*vcast = append(*vcast, l)
|
// 7. invalid type
|
||||||
|
if l.Type == NONE {
|
||||||
|
return &LineError{n, ErrInvalidSyntax}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9. 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
|
return nil
|
||||||
|
|
|
@ -29,13 +29,13 @@ func TestEachLineType(t *testing.T) {
|
||||||
{"\tinclude ./file/*.conf;\n", []string{"./file/*.conf"}, INCLUDE},
|
{"\tinclude ./file/*.conf;\n", []string{"./file/*.conf"}, INCLUDE},
|
||||||
{" \t include ./file/*.conf;\n", []string{"./file/*.conf"}, INCLUDE},
|
{" \t include ./file/*.conf;\n", []string{"./file/*.conf"}, INCLUDE},
|
||||||
|
|
||||||
{"sectionname {\n", []string{"sectionname"}, SECTION},
|
{"sectionname {\n}\n", []string{"sectionname"}, SECTION},
|
||||||
{"section-name {\n", []string{"section-name"}, SECTION},
|
{"section-name {\n}\n", []string{"section-name"}, SECTION},
|
||||||
{"section_name {\n", []string{"section_name"}, SECTION},
|
{"section_name {\n}\n", []string{"section_name"}, SECTION},
|
||||||
{"sectionname { \n", []string{"sectionname"}, SECTION},
|
{"sectionname { \n}\n", []string{"sectionname"}, SECTION},
|
||||||
{"sectionname {\t\n", []string{"sectionname"}, SECTION},
|
{"sectionname {\t\n}\n", []string{"sectionname"}, SECTION},
|
||||||
{"\tsectionname {\n", []string{"sectionname"}, SECTION},
|
{"\tsectionname {\n}\n", []string{"sectionname"}, SECTION},
|
||||||
{" \t sectionname {\n", []string{"sectionname"}, SECTION},
|
{" \t sectionname {\n}\n", []string{"sectionname"}, SECTION},
|
||||||
|
|
||||||
{"#some comment\n", []string{"some comment"}, COMMENT},
|
{"#some comment\n", []string{"some comment"}, COMMENT},
|
||||||
{"#some\tcomment\n", []string{"some\tcomment"}, COMMENT},
|
{"#some\tcomment\n", []string{"some\tcomment"}, COMMENT},
|
||||||
|
@ -58,40 +58,105 @@ func TestEachLineType(t *testing.T) {
|
||||||
decoder := parser.NewDecoder(strings.NewReader(test.Raw))
|
decoder := parser.NewDecoder(strings.NewReader(test.Raw))
|
||||||
|
|
||||||
// 2. Decode
|
// 2. Decode
|
||||||
receiver := []*Line{}
|
receiver := new(nginx)
|
||||||
err := decoder.Decode(&receiver)
|
err := decoder.Decode(receiver)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("[%d] unexpected error <%s>", i, err)
|
t.Errorf("[%d] unexpected error <%s>", i, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(receiver) != 1 {
|
if len(receiver.Lines) != 1 {
|
||||||
t.Errorf("[%d] expected only 1 element, got %d", i, len(receiver))
|
t.Errorf("[%d] expected only 1 element, got %d", i, len(receiver.Lines))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if receiver[0].Type != test.Type {
|
if receiver.Lines[0].Type != test.Type {
|
||||||
t.Errorf("[%d] expected type %d, got %d", i, test.Type, receiver[0].Type)
|
t.Errorf("[%d] expected type %d, got %d", i, test.Type, receiver.Lines[0].Type)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if receiver[0].Components == nil && test.Components != nil {
|
if receiver.Lines[0].Components == nil && test.Components != nil {
|
||||||
t.Errorf("[%d] expected components not to be null", i)
|
t.Errorf("[%d] expected components not to be null", i)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(receiver[0].Components) != len(test.Components) {
|
if len(receiver.Lines[0].Components) != len(test.Components) {
|
||||||
t.Errorf("[%d] expected %d components, got %d", i, len(test.Components), len(receiver[0].Components))
|
t.Errorf("[%d] expected %d components, got %d", i, len(test.Components), len(receiver.Lines[0].Components))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// check each component individually
|
// check each component individually
|
||||||
for c, comp := range receiver[0].Components {
|
for c, comp := range receiver.Lines[0].Components {
|
||||||
if comp != test.Components[c] {
|
if comp != test.Components[c] {
|
||||||
t.Errorf("[%d] expected component %d to be '%s', got '%s'", i, c, test.Components[c], comp)
|
t.Errorf("[%d] expected component %d to be '%s', got '%s'", i, c, test.Components[c], comp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNestedSections(t *testing.T) {
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
Raw string
|
||||||
|
SectionChain []string
|
||||||
|
}{
|
||||||
|
|
||||||
|
{"a{\n}\n", []string{"a"}},
|
||||||
|
{"a{\n\n}\n", []string{"a"}},
|
||||||
|
{"a{\n; some comment\n}\n", []string{"a"}},
|
||||||
|
{"a{\n; some comment\n #another comment\n}\n", []string{"a"}},
|
||||||
|
{"a{\nb{\n}\n}\n", []string{"a", "b"}},
|
||||||
|
{"a{\n\tb{\n\t}\n}\n", []string{"a", "b"}},
|
||||||
|
{"a{\n\tb{\n\t\tc{\n\t\t}\n\t}\n}\n", []string{"a", "b", "c"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
|
||||||
|
// 1. create reader
|
||||||
|
parser := new(nginx)
|
||||||
|
decoder := parser.NewDecoder(strings.NewReader(test.Raw))
|
||||||
|
|
||||||
|
// 2. Decode
|
||||||
|
receiver := new(nginx)
|
||||||
|
err := decoder.Decode(receiver)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("[%d] unexpected error <%s>", i, err)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(receiver.Lines) != 1 {
|
||||||
|
t.Errorf("[%d] expected only 1 element, got %d", i, len(receiver.Lines))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if receiver.Lines[0].Type != SECTION {
|
||||||
|
t.Errorf("[%d] expected type %d, got %d", i, SECTION, receiver.Lines[0].Type)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check each component individually
|
||||||
|
var current []*Line = receiver.Lines
|
||||||
|
for s, sec := range test.SectionChain {
|
||||||
|
|
||||||
|
// check that section exists in <current>
|
||||||
|
found := false
|
||||||
|
for _, line := range current {
|
||||||
|
|
||||||
|
// found
|
||||||
|
if line.Type == SECTION && line.Components[0] == sec {
|
||||||
|
found = true
|
||||||
|
current = line.Lines
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Errorf("[%d] key '%s' not found\n", i, strings.Join(test.SectionChain[:s+1], "/"))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package nginx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"git.xdrm.io/go/nix-amer/internal/clifmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LineError wraps errors with a line index
|
||||||
|
type LineError struct {
|
||||||
|
Line int
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements Error
|
||||||
|
func (le LineError) Error() string {
|
||||||
|
return fmt.Sprintf(":%d %s", le.Line, clifmt.Color(31, le.Err.Error()))
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ const (
|
||||||
ASSIGNMENT
|
ASSIGNMENT
|
||||||
INCLUDE
|
INCLUDE
|
||||||
SECTION
|
SECTION
|
||||||
|
SECTIONEND
|
||||||
)
|
)
|
||||||
|
|
||||||
type Line struct {
|
type Line struct {
|
||||||
|
@ -30,8 +31,8 @@ type Line struct {
|
||||||
// Components of the line
|
// Components of the line
|
||||||
Components []string
|
Components []string
|
||||||
|
|
||||||
// Children of the current section (nil if not a section)
|
// Lines children of the current section (nil if not a section)
|
||||||
Children []Line
|
Lines []*Line
|
||||||
|
|
||||||
// Indent is the indentation characters
|
// Indent is the indentation characters
|
||||||
Indent string
|
Indent string
|
||||||
|
|
Loading…
Reference in New Issue