diff --git a/internal/cnf/nginx.go b/internal/cnf/nginx.go index 6594348..a2e2581 100644 --- a/internal/cnf/nginx.go +++ b/internal/cnf/nginx.go @@ -7,14 +7,14 @@ import ( ) type nginx struct { - data *lib.Model + data *lib.Line parsed bool } // ReadFrom implements io.ReaderFrom func (d *nginx) ReadFrom(_reader io.Reader) (int64, error) { - d.data = new(lib.Model) + d.data = new(lib.Line) // 1. get nginx decoder decoder := lib.NewDecoder(_reader) @@ -35,68 +35,107 @@ func (d *nginx) WriteTo(_writer io.Writer) (int64, error) { } // browse returns the target of a dot-separated path (as an interface{} chain where the last is the target if found) -func (d *nginx) browse(_path string) ([][]*lib.Line, bool) { +// if is true, create if does not exist +func (d *nginx) browse(_path string, create ...bool) (*lib.Line, bool) { + + must_create := len(create) > 0 && create[0] // 1. extract path path := strings.Split(_path, ".") - if len(path) > 0 { // remove last field - path = path[:len(path)-1] + + // 2. nothing + if len(path) < 1 { + return &lib.Line{}, true } - // 2. init output chain - var current []*lib.Line = d.data.Lines - chain := make([][]*lib.Line, 0, len(path)+1) - chain = append(chain, current) + // 3. init output chain + var current *lib.Line = d.data - // 3. iterate over path / nested fields - for _, field := range path { + // 4. iterate over path / nested fields + l := len(path) + for i, field := range path { - for _, child := range current { - if child.Type == lib.SECTION && child.Components[0] == field { - current = child.Lines - chain = append(chain, current) - break + // 5. intermediary fields (sections) + if i < l-1 { + found := false + for _, child := range current.Lines { + if child.Type == lib.SECTION && len(child.Components) > 0 && child.Components[0] == field { + found = true + current = child + break + } + } + + if found { + continue + } + + // create section + if must_create { + sec := &lib.Line{ + Type: lib.SECTION, + Components: []string{field}, + Lines: make([]*lib.Line, 0), + } + current.Lines = append(current.Lines, sec) + continue + } + + // not found and not create + return nil, false + } + + // 6. last field (assignment) + for _, child := range current.Lines { + if child.Type == lib.ASSIGNMENT && len(child.Components) > 0 && child.Components[0] == field { + return child, true } } - return chain, false + // create assignment + if must_create { + assignment := &lib.Line{ + Type: lib.ASSIGNMENT, + Components: []string{field, ""}, + Lines: nil, + } + current.Lines = append(current.Lines, assignment) + return assignment, true + } + + // not found and not create + return nil, false + } - return chain, true + return nil, false } // Get returns the value of a dot-separated path, and if it exists func (d *nginx) Get(_path string) (string, bool) { // 1. browse path - chain, found := d.browse(_path) - if !found { + last, found := d.browse(_path, true) + if !found || len(last.Components) < 2 { return "", false } // 2. Get last field - fields := strings.Split(_path, ".") - field := fields[len(fields)-1] - - // 3. search in last chain element - last := chain[len(chain)-1] - - for _, line := range last { - - // 4. return value - if line.Type == lib.ASSIGNMENT && line.Components[0] == field { - return line.Components[1], true - } - - } - - return "", false + return last.Components[1], true } // Set the value of a dot-separated path, and creates it if not found func (d *nginx) Set(_path, _value string) bool { - return false + // 1. browse path + last, found := d.browse(_path, true) + if !found || len(last.Components) < 2 { + return false + } + + // 2. Set value + last.Components[1] = _value + return true } diff --git a/internal/cnf/nginx_test.go b/internal/cnf/nginx_test.go new file mode 100644 index 0000000..0ebb130 --- /dev/null +++ b/internal/cnf/nginx_test.go @@ -0,0 +1,153 @@ +package cnf + +import ( + "bytes" + "testing" +) + +func TestNginxGet(t *testing.T) { + + tests := []struct { + raw string + key string + }{ + {"key value;\n", "key"}, + {"section {\n\tkey value;\n}\n", "section.key"}, + {"ignore {\n\tignore xxx;\n}\nsection {\n\tkey value;\n}\n", "section.key"}, + } + + for i, test := range tests { + + parser := new(nginx) + reader := bytes.NewBufferString(test.raw) + + // try to extract value + _, err := parser.ReadFrom(reader) + if err != nil { + t.Errorf("[%d] parse error: %s", i, err) + continue + } + + // extract value + value, found := parser.Get(test.key) + if !found { + t.Errorf("[%d] expected a result, got none", i) + continue + } + + // check value + if value != "value" { + t.Errorf("[%d] expected 'value' got '%s'", i, value) + } + + } + +} + +func TestNginxSetPathExists(t *testing.T) { + + tests := []struct { + raw string + key string + value string + }{ + + {"key value;\n", "key", "newvalue"}, + {"section {\n\tkey value;\n}\n", "section.key", "newvalue"}, + {"ignore {\n\tignore xxx;\n}\nsection {\n\tkey value;\n}\n", "section.key", "newvalue"}, + } + + for i, test := range tests { + + parser := new(nginx) + reader := bytes.NewBufferString(test.raw) + + // try to extract value + _, err := parser.ReadFrom(reader) + if err != nil { + t.Errorf("[%d] parse error: %s", i, err) + continue + } + + // update value + if !parser.Set(test.key, test.value) { + t.Errorf("[%d] cannot set '%s' to '%s'", i, test.key, test.value) + continue + } + + // check new value + value, found := parser.Get(test.key) + if !found { + t.Errorf("[%d] expected a result, got none", i) + continue + } + + // check value + if value != test.value { + t.Errorf("[%d] expected '%s' got '%s'", i, test.value, value) + } + + } + +} + +func TestNginxSetCreatePath(t *testing.T) { + + tests := []struct { + raw string + key string + ignore string // path to field that must be present after transformation + value string + }{ + {"ignore xxx;\n", "key", "ignore", "newvalue"}, + {"ignore xxx;\nsection {\n\tkey value;\n}\n", "section.key", "ignore", "newvalue"}, + {"section {\n\tkey value;\n\tignore xxx;\n}\n", "section.key", "section.ignore", "newvalue"}, + {"ignoresec {\n\tignore xxx;\n}\n\nsection {\n\tkey value;\n}\n", "section.key", "ignoresec.ignore", "newvalue"}, + } + + for i, test := range tests { + + parser := new(nginx) + reader := bytes.NewBufferString(test.raw) + + // try to extract value + _, err := parser.ReadFrom(reader) + if err != nil { + t.Errorf("[%d] parse error: %s", i, err) + continue + } + + // update value + if !parser.Set(test.key, test.value) { + t.Errorf("[%d] cannot set '%s' to '%s'", i, test.key, test.value) + continue + } + + // check new value + value, found := parser.Get(test.key) + if !found { + t.Errorf("[%d] expected a result, got none", i) + continue + } + + // check value + if value != test.value { + t.Errorf("[%d] expected '%s' got '%s'", i, test.value, value) + continue + } + + // check that ignore field is still there + value, found = parser.Get(test.ignore) + if !found { + t.Errorf("[%d] expected ignore field, got none", i) + continue + } + + // check value + if value != "xxx" { + t.Errorf("[%d] expected ignore value to be '%s' got '%s'", i, "xxx", value) + continue + } + } + +} diff --git a/internal/cnf/parser/nginx/decoder.go b/internal/cnf/parser/nginx/decoder.go index 858a167..3213a3c 100644 --- a/internal/cnf/parser/nginx/decoder.go +++ b/internal/cnf/parser/nginx/decoder.go @@ -16,7 +16,7 @@ func (d *decoder) Decode(v interface{}) error { if v == nil { return ErrNullReceiver } - vcast, ok := v.(*Model) + vcast, ok := v.(*Line) if !ok { return ErrInvalidReceiver } diff --git a/internal/cnf/parser/nginx/decoder_test.go b/internal/cnf/parser/nginx/decoder_test.go index be2b8bc..6b8b35a 100644 --- a/internal/cnf/parser/nginx/decoder_test.go +++ b/internal/cnf/parser/nginx/decoder_test.go @@ -58,7 +58,7 @@ func TestEachLineType(t *testing.T) { decoder := NewDecoder(strings.NewReader(test.Raw)) // 2. Decode - receiver := new(Model) + receiver := new(Line) err := decoder.Decode(receiver) if err != nil { @@ -117,7 +117,7 @@ func TestNestedSections(t *testing.T) { decoder := NewDecoder(strings.NewReader(test.Raw)) // 2. Decode - receiver := new(Model) + receiver := new(Line) err := decoder.Decode(receiver) if err != nil { diff --git a/internal/cnf/parser/nginx/encoder.go b/internal/cnf/parser/nginx/encoder.go index d65b759..0f83b87 100644 --- a/internal/cnf/parser/nginx/encoder.go +++ b/internal/cnf/parser/nginx/encoder.go @@ -24,7 +24,7 @@ func (e *encoder) SetIndent(prefix, indent string) { func (e *encoder) Encode(v interface{}) error { // check 'v' - vcast, ok := v.(*Model) + vcast, ok := v.(*Line) if !ok { return ErrInvalidReceiver } diff --git a/internal/cnf/parser/nginx/encoder_test.go b/internal/cnf/parser/nginx/encoder_test.go index ac11ade..0394b15 100644 --- a/internal/cnf/parser/nginx/encoder_test.go +++ b/internal/cnf/parser/nginx/encoder_test.go @@ -65,7 +65,7 @@ func TestDecodeEncode(t *testing.T) { r, w := strings.NewReader(test.Input), &bytes.Buffer{} // parse input - receiver := new(Model) + receiver := new(Line) decoder := NewDecoder(r) if err := decoder.Decode(receiver); err != nil { t.Errorf("[%d] unexpected error <%s>", i, err) diff --git a/internal/cnf/parser/nginx/errors.go b/internal/cnf/parser/nginx/errors.go index 82503ee..98c643e 100644 --- a/internal/cnf/parser/nginx/errors.go +++ b/internal/cnf/parser/nginx/errors.go @@ -9,7 +9,7 @@ import ( 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") +var ErrInvalidReceiver = fmt.Errorf("receiver must be compatible with *Line") // ErrUnexpectedSectionClose is raised when reading a section close '}' without an // open section diff --git a/internal/cnf/parser/nginx/nginx.go b/internal/cnf/parser/nginx/nginx.go index 04137b8..9ac9b87 100644 --- a/internal/cnf/parser/nginx/nginx.go +++ b/internal/cnf/parser/nginx/nginx.go @@ -4,10 +4,6 @@ import ( "io" ) -type Model struct { - Lines []*Line -} - // NewDecoder implements parser.T func NewDecoder(r io.Reader) *decoder { return &decoder{reader: r}