package parser import ( "config-lsp/common" docvalues "config-lsp/doc-values" fstabdocumentation "config-lsp/handlers/fstab/documentation" "fmt" "regexp" "strings" "github.com/emirpasic/gods/maps/treemap" protocol "github.com/tliron/glsp/protocol_3_16" gods "github.com/emirpasic/gods/utils" ) var commentPattern = regexp.MustCompile(`^\s*#`) var ignoreLinePattern = regexp.MustCompile(`^\s*$`) var whitespacePattern = regexp.MustCompile(`\S+`) type MalformedLineError struct{} func (e MalformedLineError) Error() string { return "Malformed line" } type Field struct { Value string Start uint32 End uint32 } func (f Field) String() string { return fmt.Sprintf("%v [%v-%v]", f.Value, f.Start, f.End) } func (f *Field) CreateRange(fieldLine uint32) protocol.Range { return protocol.Range{ Start: protocol.Position{ Line: fieldLine, Character: f.Start, }, End: protocol.Position{ Line: fieldLine, Character: f.End, }, } } type FstabField string const ( FstabFieldSpec FstabField = "spec" FstabFieldMountPoint FstabField = "mountpoint" FstabFieldFileSystemType FstabField = "filesystemtype" FstabFieldOptions FstabField = "options" FstabFieldFreq FstabField = "freq" FstabFieldPass FstabField = "pass" ) type FstabFields struct { Spec *Field MountPoint *Field FilesystemType *Field Options *Field Freq *Field Pass *Field } func (f FstabFields) String() string { return fmt.Sprintf( "Spec: %s, MountPoint: %s, FilesystemType: %s, Options: %s, Freq: %s, Pass: %s", f.Spec, f.MountPoint, f.FilesystemType, f.Options, f.Freq, f.Pass, ) } type FstabLine struct { Line uint32 Fields FstabFields } func (e *FstabLine) CheckIsValid() []protocol.Diagnostic { diagnostics := make([]protocol.Diagnostic, 0) if e.Fields.Spec != nil { errors := fstabdocumentation.SpecField.DeprecatedCheckIsValid(e.Fields.Spec.Value) if len(errors) > 0 { diagnostics = append( diagnostics, docvalues.InvalidValuesToErrorDiagnostics(e.Line, e.Fields.Spec.Start, errors)..., ) } } if e.Fields.MountPoint != nil { errors := fstabdocumentation.MountPointField.DeprecatedCheckIsValid(e.Fields.MountPoint.Value) if len(errors) > 0 { diagnostics = append( diagnostics, docvalues.InvalidValuesToErrorDiagnostics(e.Line, e.Fields.MountPoint.Start, errors)..., ) } } var fileSystemType string = "" if e.Fields.FilesystemType != nil { errors := fstabdocumentation.FileSystemTypeField.DeprecatedCheckIsValid(e.Fields.FilesystemType.Value) if len(errors) > 0 { diagnostics = append( diagnostics, docvalues.InvalidValuesToErrorDiagnostics(e.Line, e.Fields.FilesystemType.Start, errors)..., ) } else { fileSystemType = e.Fields.FilesystemType.Value } } if e.Fields.Options != nil && fileSystemType != "" { var optionsField docvalues.DeprecatedValue if foundField, found := fstabdocumentation.MountOptionsMapField[fileSystemType]; found { optionsField = foundField } else { optionsField = fstabdocumentation.DefaultMountOptionsField } errors := optionsField.DeprecatedCheckIsValid(e.Fields.Options.Value) if len(errors) > 0 { diagnostics = append( diagnostics, docvalues.InvalidValuesToErrorDiagnostics(e.Line, e.Fields.Options.Start, errors)..., ) } } if e.Fields.Freq != nil { errors := fstabdocumentation.FreqField.DeprecatedCheckIsValid(e.Fields.Freq.Value) if len(errors) > 0 { diagnostics = append( diagnostics, docvalues.InvalidValuesToErrorDiagnostics(e.Line, e.Fields.Freq.Start, errors)..., ) } } if e.Fields.Pass != nil { errors := fstabdocumentation.PassField.DeprecatedCheckIsValid(e.Fields.Pass.Value) if len(errors) > 0 { diagnostics = append( diagnostics, docvalues.InvalidValuesToErrorDiagnostics(e.Line, e.Fields.Pass.Start, errors)..., ) } } return diagnostics } func (e FstabLine) GetFieldAtPosition(cursor uint32) FstabField { if e.Fields.Spec == nil || (cursor >= e.Fields.Spec.Start && cursor <= e.Fields.Spec.End) { return FstabFieldSpec } if e.Fields.MountPoint == nil || (cursor >= e.Fields.MountPoint.Start && cursor <= e.Fields.MountPoint.End) { return FstabFieldMountPoint } if e.Fields.FilesystemType == nil || (cursor >= e.Fields.FilesystemType.Start && cursor <= e.Fields.FilesystemType.End) { return FstabFieldFileSystemType } if e.Fields.Options == nil || (cursor >= e.Fields.Options.Start && cursor <= e.Fields.Options.End) { return FstabFieldOptions } if e.Fields.Freq == nil || (cursor >= e.Fields.Freq.Start && cursor <= e.Fields.Freq.End) { return FstabFieldFreq } return FstabFieldPass } type FstabEntryType string const ( FstabEntryTypeLine FstabEntryType = "line" FstabEntryTypeComment FstabEntryType = "comment" ) type FstabEntry struct { Type FstabEntryType Line FstabLine } type FstabParser struct { // [uint32]FstabEntry - line number to entry mapping Entries *treemap.Map } func (p *FstabParser) AddLine(line string, lineNumber int) error { fields := whitespacePattern.FindAllStringIndex(line, -1) if len(fields) == 0 { return MalformedLineError{} } var spec *Field var mountPoint *Field var filesystemType *Field var options *Field var freq *Field var pass *Field switch len(fields) { case 6: field := fields[5] start := uint32(field[0]) end := uint32(field[1]) pass = &Field{ Value: line[start:end], Start: start, End: end, } fallthrough case 5: field := fields[4] start := uint32(field[0]) end := uint32(field[1]) freq = &Field{ Value: line[start:end], Start: start, End: end, } fallthrough case 4: field := fields[3] start := uint32(field[0]) end := uint32(field[1]) options = &Field{ Value: line[start:end], Start: start, End: end, } fallthrough case 3: field := fields[2] start := uint32(field[0]) end := uint32(field[1]) filesystemType = &Field{ Value: line[start:end], Start: start, End: end, } fallthrough case 2: field := fields[1] start := uint32(field[0]) end := uint32(field[1]) mountPoint = &Field{ Value: line[start:end], Start: start, End: end, } fallthrough case 1: field := fields[0] start := uint32(field[0]) end := uint32(field[1]) spec = &Field{ Value: line[start:end], Start: start, End: end, } } entry := FstabEntry{ Type: FstabEntryTypeLine, Line: FstabLine{ Line: uint32(lineNumber), Fields: FstabFields{ Spec: spec, MountPoint: mountPoint, FilesystemType: filesystemType, Options: options, Freq: freq, Pass: pass, }, }, } p.Entries.Put(entry.Line.Line, entry) return nil } func (p *FstabParser) AddCommentLine(line string, lineNumber int) { entry := FstabLine{ Line: uint32(lineNumber), } p.Entries.Put(entry.Line, FstabEntry{ Type: FstabEntryTypeComment, Line: entry, }) } func (p *FstabParser) ParseFromContent(content string) []common.ParseError { errors := []common.ParseError{} lines := strings.Split(content, "\n") for index, line := range lines { if ignoreLinePattern.MatchString(line) { continue } if commentPattern.MatchString(line) { p.AddCommentLine(line, index) continue } err := p.AddLine(line, index) if err != nil { errors = append(errors, common.ParseError{ Line: uint32(index), Err: err, }) } } return errors } func (p *FstabParser) GetEntry(line uint32) (*FstabEntry, bool) { rawEntry, found := p.Entries.Get(line) if !found { return nil, false } entry := rawEntry.(FstabEntry) return &entry, true } func (p *FstabParser) Clear() { p.Entries = treemap.NewWith(gods.UInt32Comparator) } func (p *FstabParser) AnalyzeValues() []protocol.Diagnostic { diagnostics := []protocol.Diagnostic{} it := p.Entries.Iterator() for it.Next() { entry := it.Value().(FstabEntry) switch entry.Type { case FstabEntryTypeLine: newDiagnostics := entry.Line.CheckIsValid() if len(newDiagnostics) > 0 { diagnostics = append(diagnostics, newDiagnostics...) } case FstabEntryTypeComment: // Do nothing } } return diagnostics }