create nginx decoder (not fully working, but first step is ok)
This commit is contained in:
parent
1df8f9df9e
commit
08b8cf8493
|
@ -0,0 +1,23 @@
|
|||
package parser
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// Decoder is the common interface shared by parsers
|
||||
// cf. encoding/json, etc
|
||||
type Decoder interface {
|
||||
Decode(interface{}) error
|
||||
}
|
||||
|
||||
// Encoder is the common interface shared by parsers
|
||||
// cf. encoding/json, etc
|
||||
type Encoder interface {
|
||||
Encode(interface{}) error
|
||||
}
|
||||
|
||||
// T is the common parser interface
|
||||
type T interface {
|
||||
NewDecoder(io.Reader) Decoder
|
||||
NewEncoder(io.Writer) Decoder
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
package nginx
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ErrNullReceiver = fmt.Errorf("decoder receiver must not be null")
|
||||
var ErrInvalidReceiver = fmt.Errorf("decoder receiver must be compatible with *[]*Line")
|
||||
|
||||
// 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.(*[]*Line)
|
||||
if !ok {
|
||||
return ErrInvalidReceiver
|
||||
}
|
||||
|
||||
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 tree
|
||||
// tree := []Line{}
|
||||
|
||||
for {
|
||||
n++
|
||||
|
||||
l := &Line{Number: n, Type: NONE}
|
||||
|
||||
// 1. read line
|
||||
notrim, err := r.ReadString('\n')
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. ignore empty
|
||||
line := strings.Trim(notrim, " \t\r\n")
|
||||
if len(line) < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
// 3. get indentation
|
||||
firstChar := 0
|
||||
for ; !in_array(" \t\r\n", notrim[firstChar:][0]); firstChar++ {
|
||||
}
|
||||
l.Indent = notrim[0:firstChar]
|
||||
|
||||
// 3. 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")}
|
||||
}
|
||||
|
||||
// 4. section
|
||||
if l.Type == NONE {
|
||||
match := reSection.FindStringSubmatch(line)
|
||||
if match != nil {
|
||||
l.Type = SECTION
|
||||
l.Components = match[1:]
|
||||
}
|
||||
}
|
||||
|
||||
// 5. include
|
||||
if l.Type == NONE {
|
||||
match := reInclude.FindStringSubmatch(line)
|
||||
if match != nil {
|
||||
l.Type = INCLUDE
|
||||
l.Components = []string{match[1]}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. assignment
|
||||
if l.Type == NONE {
|
||||
match := reAssign.FindStringSubmatch(line)
|
||||
if match != nil {
|
||||
l.Type = ASSIGNMENT
|
||||
l.Components = match[1:]
|
||||
}
|
||||
}
|
||||
|
||||
*vcast = append(*vcast, l)
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func in_array(haystack string, needle byte) bool {
|
||||
for i, l := 0, len(haystack); i < l; i++ {
|
||||
if haystack[i] == needle {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package nginx
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEachLineType(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
Raw string
|
||||
Components []string
|
||||
Type LineType
|
||||
}{
|
||||
{"key value;\n", []string{"key", "value"}, ASSIGNMENT},
|
||||
{"key value;\n", []string{"key", "value"}, ASSIGNMENT},
|
||||
{"key \t value;\n", []string{"key", "value"}, ASSIGNMENT},
|
||||
{"key\tvalue;\n", []string{"key", "value"}, ASSIGNMENT},
|
||||
{"ke-y value;\n", []string{"ke-y", "value"}, ASSIGNMENT},
|
||||
{"ke_y value;\n", []string{"ke_y", "value"}, ASSIGNMENT},
|
||||
{"key value; \n", []string{"key", "value"}, ASSIGNMENT},
|
||||
{"key value;\t\n", []string{"key", "value"}, ASSIGNMENT},
|
||||
{"\tkey value;\n", []string{"key", "value"}, ASSIGNMENT},
|
||||
{" \t key value;\n", []string{"key", "value"}, ASSIGNMENT},
|
||||
|
||||
{"include ./file/*.conf;\n", []string{"./file/*.conf"}, INCLUDE},
|
||||
{"include ./file/*.conf; \n", []string{"./file/*.conf"}, INCLUDE},
|
||||
{"include ./file/*.conf;\t\n", []string{"./file/*.conf"}, INCLUDE},
|
||||
{"\tinclude ./file/*.conf;\n", []string{"./file/*.conf"}, INCLUDE},
|
||||
{" \t include ./file/*.conf;\n", []string{"./file/*.conf"}, INCLUDE},
|
||||
|
||||
{"sectionname {\n", []string{"sectionname"}, SECTION},
|
||||
{"section-name {\n", []string{"section-name"}, SECTION},
|
||||
{"section_name {\n", []string{"section_name"}, SECTION},
|
||||
{"sectionname { \n", []string{"sectionname"}, SECTION},
|
||||
{"sectionname {\t\n", []string{"sectionname"}, SECTION},
|
||||
{"\tsectionname {\n", []string{"sectionname"}, SECTION},
|
||||
{" \t sectionname {\n", []string{"sectionname"}, SECTION},
|
||||
|
||||
{"#some comment\n", []string{"some comment"}, COMMENT},
|
||||
{"#some\tcomment\n", []string{"some\tcomment"}, COMMENT},
|
||||
{"# some comment \n", []string{"some comment"}, COMMENT},
|
||||
{"# some comment \t\n", []string{"some comment"}, COMMENT},
|
||||
{"\t# some comment {\n", []string{"some comment {"}, COMMENT},
|
||||
|
||||
{";some comment\n", []string{"some comment"}, COLONCOMMENT},
|
||||
{"; some\tcomment\n", []string{"some\tcomment"}, COLONCOMMENT},
|
||||
{"; some comment\n", []string{"some comment"}, COLONCOMMENT},
|
||||
{"; some comment \n", []string{"some comment"}, COLONCOMMENT},
|
||||
{"; some comment \t\n", []string{"some comment"}, COLONCOMMENT},
|
||||
{"\t; some comment {\n", []string{"some comment {"}, COLONCOMMENT},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
|
||||
// 1. create reader
|
||||
parser := new(nginx)
|
||||
decoder := parser.NewDecoder(strings.NewReader(test.Raw))
|
||||
|
||||
// 2. Decode
|
||||
receiver := []*Line{}
|
||||
err := decoder.Decode(&receiver)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("[%d] unexpected error <%s>", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(receiver) != 1 {
|
||||
t.Errorf("[%d] expected only 1 element, got %d", i, len(receiver))
|
||||
continue
|
||||
}
|
||||
|
||||
if receiver[0].Type != test.Type {
|
||||
t.Errorf("[%d] expected type %d, got %d", i, test.Type, receiver[0].Type)
|
||||
continue
|
||||
}
|
||||
|
||||
if receiver[0].Components == nil && test.Components != nil {
|
||||
t.Errorf("[%d] expected components not to be null", i)
|
||||
continue
|
||||
}
|
||||
if len(receiver[0].Components) != len(test.Components) {
|
||||
t.Errorf("[%d] expected %d components, got %d", i, len(test.Components), len(receiver[0].Components))
|
||||
continue
|
||||
}
|
||||
|
||||
// check each component individually
|
||||
for c, comp := range receiver[0].Components {
|
||||
if comp != test.Components[c] {
|
||||
t.Errorf("[%d] expected component %d to be '%s', got '%s'", i, c, test.Components[c], comp)
|
||||
continue
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package nginx
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultPrefix = ""
|
||||
defaultIndent = " "
|
||||
)
|
||||
|
||||
// encoder implements parser.Encoder
|
||||
type encoder struct {
|
||||
writer io.Writer
|
||||
prefix string
|
||||
indent string
|
||||
}
|
||||
|
||||
func (e *encoder) SetIndent(prefix, indent string) {
|
||||
e.prefix = prefix
|
||||
e.indent = indent
|
||||
}
|
||||
|
||||
func (e *encoder) Encode(v interface{}) error {
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package nginx
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type LineType byte
|
||||
|
||||
const (
|
||||
NONE LineType = iota
|
||||
COMMENT // # comment
|
||||
COLONCOMMENT // ; comment
|
||||
ASSIGNMENT
|
||||
INCLUDE
|
||||
SECTION
|
||||
)
|
||||
|
||||
type Line struct {
|
||||
// Number of the line in the input file
|
||||
Number int
|
||||
|
||||
// Type of line
|
||||
Type LineType
|
||||
|
||||
// Path is the absolute dot-separated path to this line
|
||||
// "" | at the root of the file
|
||||
// "a.b" | inside the 'a' section inside the 'a' section
|
||||
Path string
|
||||
|
||||
// Components of the line
|
||||
Components []string
|
||||
|
||||
// Children of the current section (nil if not a section)
|
||||
Children []Line
|
||||
|
||||
// Indent is the indentation characters
|
||||
Indent string
|
||||
}
|
||||
|
||||
type nginx struct {
|
||||
Lines []*Line
|
||||
}
|
||||
|
||||
func (n *nginx) NewDecoder(r io.Reader) *decoder {
|
||||
return &decoder{reader: r}
|
||||
}
|
Loading…
Reference in New Issue