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 } func (d *decoder) Decode(v interface{}) error { // check 'v' if v == nil { return ErrNullReceiver } vcast, ok := v.(*nginx) if !ok { return ErrInvalidReceiver } vcast.Lines = make([]*Line, 0) 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 stack stack := make([]*Line, 0) 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 &LineError{n, err} } notrim = strings.Trim(notrim, "\r\n") // 2. ignore empty line := strings.Trim(notrim, " \t") if len(line) < 1 { continue } // 3. get indentation var firstChar int for firstChar = 0; inArray(" \t", notrim[firstChar]); 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")} } else if line[0] == '}' { l.Type = SECTIONEND if len(stack) < 1 { return &LineError{n, ErrUnexpectedSectionClose} } } // 4. section if l.Type == NONE { match := reSection.FindStringSubmatch(line) if match != nil { l.Type = SECTION l.Components = match[1:] l.Lines = make([]*Line, 0) stack = append(stack, l) } } // 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:] } } // 7. invalid type if l.Type == NONE { return &LineError{n, ErrInvalidSyntax} } // 8. pop section if l.Type == SECTIONEND { cur := stack[len(stack)-1] // pop stack = stack[0 : len(stack)-1] // pop var last []*Line if len(stack) > 0 { last = stack[len(stack)-1].Lines } else { last = vcast.Lines } last = append(last, cur) continue } // 9. add to section / or receiver stacklen := len(stack) if stacklen > 0 && l.Type == SECTION { stacklen-- } if stacklen > 0 { tail := stack[stacklen-1] tail.Lines = append(tail.Lines, l) } else { vcast.Lines = append(vcast.Lines, l) } } // fail on unclosed section if len(stack) > 0 { return ErrUnclosedSection } return nil } func inArray(haystack string, needle byte) bool { for i, l := 0, len(haystack); i < l; i++ { if haystack[i] == needle { return true } } return false }