package wireguard import ( "regexp" "slices" "strings" ) var commentPattern = regexp.MustCompile(`^\s*(;|#)`) var emptyLinePattern = regexp.MustCompile(`^\s*$`) var headerPattern = regexp.MustCompile(`^\s*\[`) type characterLocation struct { Start uint32 End uint32 } type wireguardParser struct { Sections []wireguardSection // Used to identify where not to show diagnostics CommentLines map[uint32]struct{} } func createWireguardParser() wireguardParser { return wireguardParser{ Sections: []wireguardSection{}, CommentLines: map[uint32]struct{}{}, } } type lineType string const ( LineTypeComment lineType = "comment" LineTypeEmpty lineType = "empty" LineTypeHeader lineType = "header" LineTypeProperty lineType = "property" ) func getLineType(line string) lineType { if commentPattern.MatchString(line) { return LineTypeComment } if emptyLinePattern.MatchString(line) { return LineTypeEmpty } if headerPattern.MatchString(line) { return LineTypeHeader } return LineTypeProperty } func (p *wireguardParser) parseFromString(input string) []lineError { errors := []lineError{} lines := strings.Split( input, "\n", ) slices.Reverse(lines) collectedProperties := wireguardProperties{} var lastPropertyLine *uint32 var earliestPropertyLine *uint32 for index, line := range lines { currentLineNumber := uint32(len(lines) - index - 1) lineType := getLineType(line) switch lineType { case LineTypeComment: p.CommentLines[currentLineNumber] = struct{}{} case LineTypeEmpty: continue case LineTypeProperty: err := collectedProperties.AddLine(currentLineNumber, line) if err != nil { errors = append(errors, lineError{ Line: currentLineNumber, Err: err, }) continue } earliestPropertyLine = ¤tLineNumber if lastPropertyLine == nil { lastPropertyLine = ¤tLineNumber } case LineTypeHeader: var lastLine uint32 if lastPropertyLine == nil { // Current line lastLine = currentLineNumber } else { lastLine = *lastPropertyLine } section := createWireguardSection( currentLineNumber, lastLine, line, collectedProperties, ) p.Sections = append(p.Sections, section) // Reset collectedProperties = wireguardProperties{} lastPropertyLine = nil } } if len(collectedProperties) > 0 { p.Sections = append(p.Sections, wireguardSection{ StartLine: *earliestPropertyLine, EndLine: *lastPropertyLine, Name: nil, Properties: collectedProperties, }) } // Since we parse the content from bottom to top, // we need to reverse the order slices.Reverse(p.Sections) return errors } func (p *wireguardParser) getTypeByLine(line uint32) lineType { // Check if line is a comment if _, found := p.CommentLines[line]; found { return LineTypeComment } // Check if line is a section for _, section := range p.Sections { if section.StartLine <= line && section.EndLine >= line { if section.StartLine == line && section.Name != nil { return LineTypeHeader } // Check for properties for propertyLineNumber := range section.Properties { if propertyLineNumber == line { return LineTypeProperty } } } } return LineTypeEmpty } // Get the section that the line belongs to // Example: // [Interface] // Address = 10.0.0.1 // // // [Peer] // // This would return the section [Interface] func (p *wireguardParser) getBelongingSectionByLine(line uint32) *wireguardSection { // Create a copy sections := append([]wireguardSection{}, p.Sections...) slices.Reverse(sections) for _, section := range sections { if section.StartLine <= line && section.Name != nil { return §ion } } // Global section return nil }