288 lines
5.8 KiB
Go

package fstab
import (
"config-lsp/common"
docvalues "config-lsp/doc-values"
"fmt"
"regexp"
"slices"
"strings"
protocol "github.com/tliron/glsp/protocol_3_16"
)
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 f.Value
}
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 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 FstabEntry struct {
Line uint32
Fields FstabFields
}
func (e *FstabEntry) CheckIsValid() []protocol.Diagnostic {
println(fmt.Sprintf("Checking entry at line %d; fields: %v", e.Line, e.Fields))
diagnostics := make([]protocol.Diagnostic, 0)
severity := protocol.DiagnosticSeverityError
if e.Fields.Spec != nil {
err := specField.CheckIsValid(e.Fields.Spec.Value)
if err != nil {
diagnostics = append(diagnostics, protocol.Diagnostic{
Range: e.Fields.Spec.CreateRange(e.Line),
Message: err.Error(),
Severity: &severity,
})
}
}
if e.Fields.MountPoint != nil {
err := mountPointField.CheckIsValid(e.Fields.MountPoint.Value)
if err != nil {
diagnostics = append(diagnostics, protocol.Diagnostic{
Range: e.Fields.Spec.CreateRange(e.Line),
Message: err.Error(),
Severity: &severity,
})
}
}
var fileSystemType string = ""
if e.Fields.FilesystemType != nil {
err := fileSystemTypeField.CheckIsValid(e.Fields.FilesystemType.Value)
if err != nil {
diagnostics = append(diagnostics, protocol.Diagnostic{
Range: e.Fields.FilesystemType.CreateRange(e.Line),
Message: err.Error(),
Severity: &severity,
})
} else {
fileSystemType = e.Fields.FilesystemType.Value
}
}
if e.Fields.Options != nil && fileSystemType != "" {
var optionsField docvalues.Value
if foundField, found := mountOptionsMapField[fileSystemType]; found {
optionsField = foundField
} else {
optionsField = defaultMountOptionsField
}
println(fmt.Sprintf("Checking options for %s", fileSystemType))
err := optionsField.CheckIsValid(e.Fields.Options.Value)
if err != nil {
diagnostics = append(diagnostics, protocol.Diagnostic{
Range: e.Fields.Options.CreateRange(e.Line),
Message: err.Error(),
Severity: &severity,
})
}
}
return diagnostics
}
type FstabParser struct {
entries []FstabEntry
}
func (p *FstabParser) AddLine(line string, lineNumber int) error {
fields := whitespacePattern.Split(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:
value := fields[5]
start := uint32(strings.Index(line, value))
pass = Field{
Value: fields[5],
Start: start,
End: start + uint32(len(value)),
}
fallthrough
case 5:
value := fields[4]
start := uint32(strings.Index(line, value))
freq = Field{
Value: value,
Start: start,
End: start + uint32(len(value)),
}
fallthrough
case 4:
value := fields[3]
start := uint32(strings.Index(line, value))
options = Field{
Value: value,
Start: start,
End: start + uint32(len(value)),
}
fallthrough
case 3:
value := fields[2]
start := uint32(strings.Index(line, value))
filesystemType = Field{
Value: value,
Start: start,
End: start + uint32(len(value)),
}
fallthrough
case 2:
value := fields[1]
start := uint32(strings.Index(line, value))
mountPoint = Field{
Value: value,
Start: start,
End: start + uint32(len(value)),
}
fallthrough
case 1:
value := fields[0]
start := uint32(strings.Index(line, value))
spec = Field{
Value: value,
Start: start,
End: start + uint32(len(value)),
}
}
entry := FstabEntry{
Line: uint32(lineNumber),
Fields: FstabFields{
Spec: &spec,
MountPoint: &mountPoint,
FilesystemType: &filesystemType,
Options: &options,
Freq: &freq,
Pass: &pass,
},
}
p.entries = append(p.entries, entry)
return nil
}
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
}
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) {
index, found := slices.BinarySearchFunc(p.entries, line, func(entry FstabEntry, line uint32) int {
if entry.Line < line {
return -1
}
if entry.Line > line {
return 1
}
return 0
})
if !found {
return FstabEntry{}, false
}
return p.entries[index], true
}
func (p *FstabParser) Clear() {
p.entries = []FstabEntry{}
}
func (p *FstabParser) AnalyzeValues() []protocol.Diagnostic {
diagnostics := []protocol.Diagnostic{}
for _, entry := range p.entries {
newDiagnostics := entry.CheckIsValid()
if len(newDiagnostics) > 0 {
diagnostics = append(diagnostics, newDiagnostics...)
}
}
return diagnostics
}