implement nginx/encoder + decode->encode test
This commit is contained in:
parent
69f7cb47b8
commit
0d6067dda5
|
@ -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 }
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue