implement nginx/encoder + decode->encode test
This commit is contained in:
parent
69f7cb47b8
commit
0d6067dda5
|
@ -2,29 +2,11 @@ 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 }
|
||||
|
||||
|
|
|
@ -54,8 +54,7 @@ func TestEachLineType(t *testing.T) {
|
|||
for i, test := range tests {
|
||||
|
||||
// 1. create reader
|
||||
parser := new(nginx)
|
||||
decoder := parser.NewDecoder(strings.NewReader(test.Raw))
|
||||
decoder := NewDecoder(strings.NewReader(test.Raw))
|
||||
|
||||
// 2. Decode
|
||||
receiver := new(nginx)
|
||||
|
@ -114,8 +113,7 @@ func TestNestedSections(t *testing.T) {
|
|||
for i, test := range tests {
|
||||
|
||||
// 1. create reader
|
||||
parser := new(nginx)
|
||||
decoder := parser.NewDecoder(strings.NewReader(test.Raw))
|
||||
decoder := NewDecoder(strings.NewReader(test.Raw))
|
||||
|
||||
// 2. Decode
|
||||
receiver := new(nginx)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package nginx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
|
@ -22,5 +23,91 @@ func (e *encoder) SetIndent(prefix, indent string) {
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
||||
// 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
|
||||
type LineError struct {
|
||||
Line int
|
||||
|
|
|
@ -9,10 +9,15 @@ type nginx struct {
|
|||
}
|
||||
|
||||
// NewDecoder implements parser.T
|
||||
func (n *nginx) NewDecoder(r io.Reader) *decoder {
|
||||
func NewDecoder(r io.Reader) *decoder {
|
||||
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
|
||||
// nil is returned if no match found
|
||||
func (l *nginx) Section(name string) *Line {
|
||||
|
|
Loading…
Reference in New Issue