implement nginx/encoder + decode->encode test

This commit is contained in:
xdrm-brackets 2018-11-12 16:42:24 +01:00
parent 69f7cb47b8
commit 0d6067dda5
6 changed files with 214 additions and 23 deletions

View File

@ -2,29 +2,11 @@ package nginx
import ( import (
"bufio" "bufio"
"fmt"
"io" "io"
"regexp" "regexp"
"strings" "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 // decoder implements parser.Decoder
type decoder struct{ reader io.Reader } type decoder struct{ reader io.Reader }

View File

@ -54,8 +54,7 @@ func TestEachLineType(t *testing.T) {
for i, test := range tests { for i, test := range tests {
// 1. create reader // 1. create reader
parser := new(nginx) decoder := NewDecoder(strings.NewReader(test.Raw))
decoder := parser.NewDecoder(strings.NewReader(test.Raw))
// 2. Decode // 2. Decode
receiver := new(nginx) receiver := new(nginx)
@ -114,8 +113,7 @@ func TestNestedSections(t *testing.T) {
for i, test := range tests { for i, test := range tests {
// 1. create reader // 1. create reader
parser := new(nginx) decoder := NewDecoder(strings.NewReader(test.Raw))
decoder := parser.NewDecoder(strings.NewReader(test.Raw))
// 2. Decode // 2. Decode
receiver := new(nginx) receiver := new(nginx)

View File

@ -1,6 +1,7 @@
package nginx package nginx
import ( import (
"fmt"
"io" "io"
) )
@ -22,5 +23,91 @@ func (e *encoder) SetIndent(prefix, indent string) {
} }
func (e *encoder) Encode(v interface{}) error { func (e *encoder) Encode(v interface{}) error {
// check 'v'
vcast, ok := v.(*nginx)
if !ok {
return ErrInvalidReceiver
}
// empty config
if len(vcast.Lines) < 1 {
return nil
}
// actual indentation level
indent := 0
// actual lines to write
stack := make([]*Line, 0)
// push nginx.Lines in reverse order
for l := len(vcast.Lines) - 1; l >= 0; l-- {
stack = append(stack, vcast.Lines[l])
}
for len(stack) > 0 {
// pop stack
line := stack[len(stack)-1]
stack = stack[:len(stack)-1]
// line representation
repr := make([]byte, 0)
if line == nil { // section end
indent--
}
// 1. add indentation
repr = append(repr, []byte(e.prefix)...)
for i := 0; i < indent; i++ {
repr = append(repr, []byte(e.indent)...)
}
// 2. section end
if line == nil {
repr = append(repr, []byte("}\n\n")...)
// Note: new line after section for readability
indent--
// 3. comments
} else if line.Type == COMMENT {
repr = append(repr, []byte(fmt.Sprintf("#%s\n", line.Components[0]))...)
} else if line.Type == COLONCOMMENT {
repr = append(repr, []byte(fmt.Sprintf(";%s\n", line.Components[0]))...)
// 4.assignments
} else if line.Type == ASSIGNMENT {
repr = append(repr, []byte(fmt.Sprintf("%s %s;\n", line.Components[0], line.Components[1]))...)
// 5. include
} else if line.Type == INCLUDE {
repr = append(repr, []byte(fmt.Sprintf("include %s;\n", line.Components[0]))...)
// 6. section
} else {
// push to stack
stack = append(stack, nil)
// push children lines in reverse order
if len(line.Lines) > 0 {
for l := len(line.Lines); l > 0; l-- {
stack = append(stack, line.Lines[l])
}
}
repr = append(repr, []byte(fmt.Sprintf("%s {\n", line.Components[0]))...)
indent++
}
_, err := e.writer.Write(repr)
if err != nil {
return err
}
}
return nil return nil
} }

View File

@ -0,0 +1,102 @@
package nginx
import (
"bytes"
"strings"
"testing"
)
func TestDecodeEncode(t *testing.T) {
tests := []struct {
Input string
Output string
}{
{"key value;\n", "key value;\n"},
{"key value;\n", "key value;\n"},
{"key \t value;\n", "key value;\n"},
{"key\tvalue;\n", "key value;\n"},
{"ke-y value;\n", "ke-y value;\n"},
{"ke_y value;\n", "ke_y value;\n"},
{"key value; \n", "key value;\n"},
{"key value;\t\n", "key value;\n"},
{"\tkey value;\n", "key value;\n"},
{" \t key value;\n", "key value;\n"},
{"include ./file/*.conf;\n", "include ./file/*.conf;\n"},
{"include ./file/*.conf; \n", "include ./file/*.conf;\n"},
{"include ./file/*.conf;\t\n", "include ./file/*.conf;\n"},
{"\tinclude ./file/*.conf;\n", "include ./file/*.conf;\n"},
{" \t include ./file/*.conf;\n", "include ./file/*.conf;\n"},
{"sectionname {\n}\n", "sectionname {\n}\n\n"},
{"section-name {\n}\n", "section-name {\n}\n\n"},
{"section_name {\n}\n", "section_name {\n}\n\n"},
{"sectionname { \n}\n", "sectionname {\n}\n\n"},
{"sectionname {\t\n}\n", "sectionname {\n}\n\n"},
{"\tsectionname {\n}\n", "sectionname {\n}\n\n"},
{" \t sectionname {\n}\n", "sectionname {\n}\n\n"},
{"#some comment\n", "#some comment\n"},
{"#some\tcomment\n", "#some\tcomment\n"},
{"# some comment \n", "# some comment\n"},
{"# some comment \t\n", "# some comment\n"},
{"\t# some comment {\n", "# some comment {\n"},
{";some comment\n", ";some comment\n"},
{"; some\tcomment\n", "; some\tcomment\n"},
{"; some comment\n", "; some comment\n"},
{"; some comment \n", "; some comment\n"},
{"; some comment \t\n", "; some comment\n"},
{"\t; some comment {\n", "; some comment {\n"},
}
for i, test := range tests {
// create reader/writer
r, w := strings.NewReader(test.Input), &bytes.Buffer{}
// parse input
receiver := new(nginx)
decoder := NewDecoder(r)
if err := decoder.Decode(receiver); err != nil {
t.Errorf("[%d] unexpected error <%s>", i, err)
continue
}
// encode back to writer
encoder := NewEncoder(w)
encoder.SetIndent("", "\t")
if err := encoder.Encode(receiver); err != nil {
t.Errorf("[%d] unexpected error <%s>", i, err)
continue
}
// check equality
if w.String() != test.Output {
t.Errorf("[%d] expected '%s', got '%s'", i, escape(test.Output), escape(w.String()))
}
}
}
func escape(raw string) string {
escaped := make([]rune, 0)
for _, char := range raw {
if char == '\n' {
escaped = append(escaped, []rune("\\n")...)
} else if char == '\t' {
escaped = append(escaped, []rune("\\t")...)
} else if char == '\r' {
escaped = append(escaped, []rune("\\r")...)
} else {
escaped = append(escaped, char)
}
}
return string(escaped)
}

View File

@ -5,6 +5,23 @@ import (
"git.xdrm.io/go/nix-amer/internal/clifmt" "git.xdrm.io/go/nix-amer/internal/clifmt"
) )
// 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")
// LineError wraps errors with a line index // LineError wraps errors with a line index
type LineError struct { type LineError struct {
Line int Line int

View File

@ -9,10 +9,15 @@ type nginx struct {
} }
// NewDecoder implements parser.T // NewDecoder implements parser.T
func (n *nginx) NewDecoder(r io.Reader) *decoder { func NewDecoder(r io.Reader) *decoder {
return &decoder{reader: r} return &decoder{reader: r}
} }
// NewEncoder implements parser.T
func NewEncoder(w io.Writer) *encoder {
return &encoder{writer: w}
}
// Section returns the section with a given name at the root level // Section returns the section with a given name at the root level
// nil is returned if no match found // nil is returned if no match found
func (l *nginx) Section(name string) *Line { func (l *nginx) Section(name string) *Line {