mirror of
https://github.com/Myzel394/config-lsp.git
synced 2025-06-18 23:15:26 +02:00
feat(ssh_config): Add hosts parsing; Add some tests
This commit is contained in:
parent
e6900d9861
commit
8067c821e8
@ -4,6 +4,7 @@ import (
|
|||||||
"config-lsp/common"
|
"config-lsp/common"
|
||||||
commonparser "config-lsp/common/parser"
|
commonparser "config-lsp/common/parser"
|
||||||
"config-lsp/handlers/ssh_config/ast/parser"
|
"config-lsp/handlers/ssh_config/ast/parser"
|
||||||
|
hostparser "config-lsp/handlers/ssh_config/host-parser"
|
||||||
"config-lsp/handlers/ssh_config/match-parser"
|
"config-lsp/handlers/ssh_config/match-parser"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -131,7 +132,7 @@ func (s *sshParserListener) ExitEntry(ctx *parser.EntryContext) {
|
|||||||
if len(errors) > 0 {
|
if len(errors) > 0 {
|
||||||
for _, err := range errors {
|
for _, err := range errors {
|
||||||
s.Errors = append(s.Errors, common.LSPError{
|
s.Errors = append(s.Errors, common.LSPError{
|
||||||
Range: err.Range.ShiftHorizontal(s.sshContext.currentOption.Start.Character),
|
Range: err.Range,
|
||||||
Err: err.Err,
|
Err: err.Err,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -153,6 +154,41 @@ func (s *sshParserListener) ExitEntry(ctx *parser.EntryContext) {
|
|||||||
|
|
||||||
s.sshContext.currentKeyIsBlockOf = nil
|
s.sshContext.currentKeyIsBlockOf = nil
|
||||||
s.sshContext.currentBlock = matchBlock
|
s.sshContext.currentBlock = matchBlock
|
||||||
|
case SSHBlockTypeHost:
|
||||||
|
var host *hostparser.Host
|
||||||
|
|
||||||
|
hostParser := hostparser.NewHost()
|
||||||
|
errors := hostParser.Parse(
|
||||||
|
s.sshContext.currentOption.OptionValue.Value.Raw,
|
||||||
|
location.Start.Line,
|
||||||
|
s.sshContext.currentOption.OptionValue.Start.Character,
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(errors) > 0 {
|
||||||
|
for _, err := range errors {
|
||||||
|
s.Errors = append(s.Errors, common.LSPError{
|
||||||
|
Range: err.Range,
|
||||||
|
Err: err.Err,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
host = hostParser
|
||||||
|
}
|
||||||
|
|
||||||
|
hostBlock := &SSHHostBlock{
|
||||||
|
LocationRange: location,
|
||||||
|
HostOption: s.sshContext.currentOption,
|
||||||
|
HostValue: host,
|
||||||
|
Options: treemap.NewWith(gods.UInt32Comparator),
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Config.Options.Put(
|
||||||
|
location.Start.Line,
|
||||||
|
hostBlock,
|
||||||
|
)
|
||||||
|
|
||||||
|
s.sshContext.currentKeyIsBlockOf = nil
|
||||||
|
s.sshContext.currentBlock = hostBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -127,3 +127,132 @@ Match originalhost "192.168.0.1"
|
|||||||
t.Errorf("Expected third entry to be User root, but got: %v", thirdEntry)
|
t.Errorf("Expected third entry to be User root, but got: %v", thirdEntry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSimpleHostBlock(
|
||||||
|
t *testing.T,
|
||||||
|
) {
|
||||||
|
input := utils.Dedent(`
|
||||||
|
Ciphers 3des-cbc
|
||||||
|
|
||||||
|
Host example.com
|
||||||
|
Port 22
|
||||||
|
`)
|
||||||
|
p := NewSSHConfig()
|
||||||
|
errors := p.Parse(input)
|
||||||
|
|
||||||
|
if len(errors) != 0 {
|
||||||
|
t.Fatalf("Expected no errors, got %v", errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(p.Options.Size() == 2 &&
|
||||||
|
len(utils.KeysOfMap(p.CommentLines)) == 0) {
|
||||||
|
t.Errorf("Expected 2 option and no comment lines, but got: %v entries, %v comment lines", p.Options.Size(), len(p.CommentLines))
|
||||||
|
}
|
||||||
|
|
||||||
|
rawFirstEntry, _ := p.Options.Get(uint32(0))
|
||||||
|
firstEntry := rawFirstEntry.(*SSHOption)
|
||||||
|
if !(firstEntry.Value.Value == "Ciphers 3des-cbc") {
|
||||||
|
t.Errorf("Expected first entry to be Ciphers 3des-cbc, but got: %v", firstEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
rawSecondEntry, _ := p.Options.Get(uint32(2))
|
||||||
|
secondEntry := rawSecondEntry.(*SSHHostBlock)
|
||||||
|
if !(secondEntry.Options.Size() == 1 &&
|
||||||
|
secondEntry.LocationRange.Start.Line == 2 &&
|
||||||
|
secondEntry.LocationRange.Start.Character == 0 &&
|
||||||
|
secondEntry.LocationRange.End.Line == 3 &&
|
||||||
|
secondEntry.LocationRange.End.Character == 8) {
|
||||||
|
t.Errorf("Expected second entry to be Host example.com, but got: %v", secondEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
rawThirdEntry, _ := secondEntry.Options.Get(uint32(3))
|
||||||
|
thirdEntry := rawThirdEntry.(*SSHOption)
|
||||||
|
if !(thirdEntry.Value.Raw == "\tPort 22" &&
|
||||||
|
thirdEntry.Key.Value.Raw == "Port" &&
|
||||||
|
thirdEntry.OptionValue.Value.Raw == "22" &&
|
||||||
|
thirdEntry.LocationRange.Start.Line == 3 &&
|
||||||
|
thirdEntry.LocationRange.Start.Character == 0 &&
|
||||||
|
thirdEntry.LocationRange.End.Line == 3 &&
|
||||||
|
thirdEntry.LocationRange.End.Character == 8) {
|
||||||
|
t.Errorf("Expected third entry to be Port 22, but got: %v", thirdEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
rawFourthEntry, _ := p.Options.Get(uint32(3))
|
||||||
|
|
||||||
|
if !(rawFourthEntry == nil) {
|
||||||
|
t.Errorf("Expected fourth entry to be nil, but got: %v", rawFourthEntry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComplexExample(
|
||||||
|
t *testing.T,
|
||||||
|
) {
|
||||||
|
input := utils.Dedent(`
|
||||||
|
Host laptop
|
||||||
|
HostName laptop.lan
|
||||||
|
|
||||||
|
Match originalhost laptop exec "[[ $(/usr/bin/dig +short laptop.lan) == '' ]]"
|
||||||
|
HostName laptop.sdn
|
||||||
|
`)
|
||||||
|
p := NewSSHConfig()
|
||||||
|
errors := p.Parse(input)
|
||||||
|
|
||||||
|
if len(errors) != 0 {
|
||||||
|
t.Fatalf("Expected no errors, got %v", errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(p.Options.Size() == 2 &&
|
||||||
|
len(utils.KeysOfMap(p.CommentLines)) == 0) {
|
||||||
|
t.Errorf("Expected 2 option and no comment lines, but got: %v entries, %v comment lines", p.Options.Size(), len(p.CommentLines))
|
||||||
|
}
|
||||||
|
|
||||||
|
rawFirstEntry, _ := p.Options.Get(uint32(0))
|
||||||
|
firstBlock := rawFirstEntry.(*SSHHostBlock)
|
||||||
|
if !(firstBlock.Options.Size() == 1 &&
|
||||||
|
firstBlock.LocationRange.Start.Line == 0 &&
|
||||||
|
firstBlock.LocationRange.Start.Character == 0 &&
|
||||||
|
firstBlock.LocationRange.End.Line == 1 &&
|
||||||
|
firstBlock.LocationRange.End.Character == 23) {
|
||||||
|
t.Errorf("Expected first entry to be Host laptop, but got: %v", firstBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
rawSecondEntry, _ := firstBlock.Options.Get(uint32(1))
|
||||||
|
secondOption := rawSecondEntry.(*SSHOption)
|
||||||
|
if !(secondOption.Value.Raw == " HostName laptop.lan" &&
|
||||||
|
secondOption.Key.Value.Raw == "HostName" &&
|
||||||
|
secondOption.OptionValue.Value.Raw == "laptop.lan" &&
|
||||||
|
secondOption.LocationRange.Start.Line == 1 &&
|
||||||
|
secondOption.LocationRange.Start.Character == 0 &&
|
||||||
|
secondOption.Key.LocationRange.Start.Character == 4 &&
|
||||||
|
secondOption.LocationRange.End.Line == 1 &&
|
||||||
|
secondOption.LocationRange.End.Character == 23) {
|
||||||
|
t.Errorf("Expected second entry to be HostName laptop.lan, but got: %v", secondOption)
|
||||||
|
}
|
||||||
|
|
||||||
|
rawThirdEntry, _ := p.Options.Get(uint32(3))
|
||||||
|
secondBlock := rawThirdEntry.(*SSHMatchBlock)
|
||||||
|
if !(secondBlock.Options.Size() == 1 &&
|
||||||
|
secondBlock.LocationRange.Start.Line == 3 &&
|
||||||
|
secondBlock.LocationRange.Start.Character == 0 &&
|
||||||
|
secondBlock.LocationRange.End.Line == 4 &&
|
||||||
|
secondBlock.LocationRange.End.Character == 23) {
|
||||||
|
t.Errorf("Expected second entry to be Match originalhost laptop exec \"[[ $(/usr/bin/dig +short laptop.lan) == '' ]]\", but got: %v", secondBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(secondBlock.MatchOption.LocationRange.End.Character == 78) {
|
||||||
|
t.Errorf("Expected second entry to be Match originalhost laptop exec \"[[ $(/usr/bin/dig +short laptop.lan) == '' ]]\", but got: %v", secondBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
rawFourthEntry, _ := secondBlock.Options.Get(uint32(4))
|
||||||
|
thirdOption := rawFourthEntry.(*SSHOption)
|
||||||
|
if !(thirdOption.Value.Raw == " HostName laptop.sdn" &&
|
||||||
|
thirdOption.Key.Value.Raw == "HostName" &&
|
||||||
|
thirdOption.OptionValue.Value.Raw == "laptop.sdn" &&
|
||||||
|
thirdOption.LocationRange.Start.Line == 4 &&
|
||||||
|
thirdOption.LocationRange.Start.Character == 0 &&
|
||||||
|
thirdOption.Key.LocationRange.Start.Character == 4 &&
|
||||||
|
thirdOption.LocationRange.End.Line == 4 &&
|
||||||
|
thirdOption.LocationRange.End.Character == 23) {
|
||||||
|
t.Errorf("Expected third entry to be HostName laptop.sdn, but got: %v", thirdOption)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,7 +3,9 @@ package ast
|
|||||||
import (
|
import (
|
||||||
"config-lsp/common"
|
"config-lsp/common"
|
||||||
commonparser "config-lsp/common/parser"
|
commonparser "config-lsp/common/parser"
|
||||||
|
hostparser "config-lsp/handlers/ssh_config/host-parser"
|
||||||
"config-lsp/handlers/ssh_config/match-parser"
|
"config-lsp/handlers/ssh_config/match-parser"
|
||||||
|
|
||||||
"github.com/emirpasic/gods/maps/treemap"
|
"github.com/emirpasic/gods/maps/treemap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -44,10 +46,10 @@ type SSHMatchBlock struct {
|
|||||||
type SSHHostBlock struct {
|
type SSHHostBlock struct {
|
||||||
common.LocationRange
|
common.LocationRange
|
||||||
HostOption *SSHOption
|
HostOption *SSHOption
|
||||||
HostValue string
|
HostValue *hostparser.Host
|
||||||
|
|
||||||
// [uint32]*SSHOption -> line number -> *SSHOption
|
// [uint32]*SSHOption -> line number -> *SSHOption
|
||||||
Others *treemap.Map
|
Options *treemap.Map
|
||||||
}
|
}
|
||||||
|
|
||||||
type SSHConfig struct {
|
type SSHConfig struct {
|
||||||
|
@ -32,7 +32,7 @@ func (b *SSHHostBlock) GetBlockType() SSHBlockType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *SSHHostBlock) AddOption(option *SSHOption) {
|
func (b *SSHHostBlock) AddOption(option *SSHOption) {
|
||||||
b.Others.Put(option.LocationRange.Start.Line, option)
|
b.Options.Put(option.LocationRange.Start.Line, option)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *SSHHostBlock) SetEnd(end common.Location) {
|
func (b *SSHHostBlock) SetEnd(end common.Location) {
|
||||||
|
16
handlers/ssh_config/host-parser/host_ast.go
Normal file
16
handlers/ssh_config/host-parser/host_ast.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package hostparser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"config-lsp/common"
|
||||||
|
commonparser "config-lsp/common/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Host struct {
|
||||||
|
Hosts []*HostValue
|
||||||
|
}
|
||||||
|
|
||||||
|
type HostValue struct {
|
||||||
|
common.LocationRange
|
||||||
|
Value commonparser.ParsedString
|
||||||
|
}
|
||||||
|
|
54
handlers/ssh_config/host-parser/parser.go
Normal file
54
handlers/ssh_config/host-parser/parser.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package hostparser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"config-lsp/common"
|
||||||
|
"regexp"
|
||||||
|
commonparser "config-lsp/common/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewHost() *Host {
|
||||||
|
match := new(Host)
|
||||||
|
match.Clear()
|
||||||
|
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Host) Clear() {
|
||||||
|
h.Hosts = make([]*HostValue, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
var textPattern = regexp.MustCompile(`\S+`)
|
||||||
|
|
||||||
|
func (h *Host) Parse(
|
||||||
|
input string,
|
||||||
|
line uint32,
|
||||||
|
startCharacter uint32,
|
||||||
|
) []common.LSPError {
|
||||||
|
hostsIndexes := textPattern.FindAllStringIndex(input, -1)
|
||||||
|
|
||||||
|
for _, hostIndex := range hostsIndexes {
|
||||||
|
startIndex := hostIndex[0]
|
||||||
|
endIndex := hostIndex[1]
|
||||||
|
|
||||||
|
rawHost := input[startIndex:endIndex]
|
||||||
|
|
||||||
|
value := commonparser.ParseRawString(rawHost, commonparser.FullFeatures)
|
||||||
|
host := HostValue{
|
||||||
|
LocationRange: common.LocationRange{
|
||||||
|
Start: common.Location{
|
||||||
|
Line: line,
|
||||||
|
Character: startCharacter + uint32(startIndex),
|
||||||
|
},
|
||||||
|
End: common.Location{
|
||||||
|
Line: line,
|
||||||
|
Character: startCharacter + uint32(endIndex),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Value: value,
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Hosts = append(h.Hosts, &host)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
73
handlers/ssh_config/host-parser/parser_test.go
Normal file
73
handlers/ssh_config/host-parser/parser_test.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package hostparser
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestSimpleExample(
|
||||||
|
t *testing.T,
|
||||||
|
) {
|
||||||
|
input := `example.com`
|
||||||
|
|
||||||
|
host := NewHost()
|
||||||
|
offset := uint32(8)
|
||||||
|
errs := host.Parse(input, 4, offset)
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("Expected no errors, got %v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(len(host.Hosts) == 1) {
|
||||||
|
t.Errorf("Expected 1 host, got %v", len(host.Hosts))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(host.Hosts[0].Value.Raw == "example.com") {
|
||||||
|
t.Errorf("Expected host to be 'example.com', got %v", host.Hosts[0].Value.Raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(host.Hosts[0].Start.Line == 4 && host.Hosts[0].Start.Character == 0+offset && host.Hosts[0].End.Character == 11+offset) {
|
||||||
|
t.Errorf("Expected host to be at line 4, characters 0-11, got %v", host.Hosts[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(host.Hosts[0].Value.Value == "example.com") {
|
||||||
|
t.Errorf("Expected host value to be 'example.com', got %v", host.Hosts[0].Value.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultipleExample(
|
||||||
|
t *testing.T,
|
||||||
|
) {
|
||||||
|
input := `example.com example.org example.net`
|
||||||
|
|
||||||
|
host := NewHost()
|
||||||
|
offset := uint32(8)
|
||||||
|
errs := host.Parse(input, 4, offset)
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("Expected no errors, got %v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(len(host.Hosts) == 3) {
|
||||||
|
t.Errorf("Expected 3 hosts, got %v", len(host.Hosts))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIncompleteExample(
|
||||||
|
t *testing.T,
|
||||||
|
) {
|
||||||
|
input := `example.com `
|
||||||
|
|
||||||
|
host := NewHost()
|
||||||
|
offset := uint32(8)
|
||||||
|
errs := host.Parse(input, 4, offset)
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("Expected no errors, got %v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(len(host.Hosts) == 1) {
|
||||||
|
t.Errorf("Expected 1 hosts, got %v", len(host.Hosts))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(host.Hosts[0].Value.Raw == "example.com") {
|
||||||
|
t.Errorf("Expected host to be 'example.com', got %v", host.Hosts[0].Value.Raw)
|
||||||
|
}
|
||||||
|
}
|
@ -126,7 +126,7 @@ func (s *sshdParserListener) ExitEntry(ctx *parser.EntryContext) {
|
|||||||
if len(errors) > 0 {
|
if len(errors) > 0 {
|
||||||
for _, err := range errors {
|
for _, err := range errors {
|
||||||
s.Errors = append(s.Errors, common.LSPError{
|
s.Errors = append(s.Errors, common.LSPError{
|
||||||
Range: err.Range.ShiftHorizontal(s.sshdContext.currentOption.Start.Character),
|
Range: err.Range,
|
||||||
Err: err.Err,
|
Err: err.Err,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user