mirror of
https://github.com/wavetermdev/ssh_config.git
synced 2026-08-05 13:43:40 -07:00
config: fix EOL whitespace for key/value pairs and Host lines
See the description in the CHANGELOG - we were handling this incorrectly and attaching whitespace to the end of values where that didn't make much sense to do.
This commit is contained in:
@@ -3,6 +3,7 @@ Dustin Spicuzza <dustin@virtualroadside.com>
|
||||
Eugene Terentev <eugene@terentev.net>
|
||||
Kevin Burke <kevin@burke.dev>
|
||||
Mark Nevill <nev@improbable.io>
|
||||
Scott Lessans <slessans@gmail.com>
|
||||
Sergey Lukjanov <me@slukjanov.name>
|
||||
Wayne Ashley Berry <wayneashleyberry@gmail.com>
|
||||
santosh653 <70637961+santosh653@users.noreply.github.com>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Changes
|
||||
|
||||
## Version 1.2
|
||||
|
||||
Previously, if a Host declaration or a value had trailing whitespace, that
|
||||
whitespace would have been included as part of the value. This led to unexpected
|
||||
consequences. For example:
|
||||
|
||||
```
|
||||
Host example # A comment
|
||||
HostName example.com # Another comment
|
||||
```
|
||||
|
||||
Prior to version 1.2, the value for Host would have been "example " and the
|
||||
value for HostName would have been "example.com ". Both of these are
|
||||
unintuitive.
|
||||
|
||||
Instead, we strip the trailing whitespace in the configuration, which leads to
|
||||
more intuitive behavior.
|
||||
@@ -498,7 +498,10 @@ type Host struct {
|
||||
// A Node is either a key/value pair or a comment line.
|
||||
Nodes []Node
|
||||
// EOLComment is the comment (if any) terminating the Host line.
|
||||
EOLComment string
|
||||
EOLComment string
|
||||
// Whitespace if any between the Host declaration and a trailing comment.
|
||||
spaceBeforeComment string
|
||||
|
||||
hasEquals bool
|
||||
leadingSpace int // TODO: handle spaces vs tabs here.
|
||||
// The file starts with an implicit "Host *" declaration.
|
||||
@@ -530,7 +533,7 @@ func (h *Host) Matches(alias string) bool {
|
||||
// String prints h as it would appear in a config file. Minor tweaks may be
|
||||
// present in the whitespace in the printed file.
|
||||
func (h *Host) String() string {
|
||||
var buf bytes.Buffer
|
||||
var buf strings.Builder
|
||||
//lint:ignore S1002 I prefer to write it this way
|
||||
if h.implicit == false {
|
||||
buf.WriteString(strings.Repeat(" ", int(h.leadingSpace)))
|
||||
@@ -546,8 +549,9 @@ func (h *Host) String() string {
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
}
|
||||
buf.WriteString(h.spaceBeforeComment)
|
||||
if h.EOLComment != "" {
|
||||
buf.WriteString(" #")
|
||||
buf.WriteByte('#')
|
||||
buf.WriteString(h.EOLComment)
|
||||
}
|
||||
buf.WriteByte('\n')
|
||||
@@ -568,12 +572,14 @@ type Node interface {
|
||||
// KV is a line in the config file that contains a key, a value, and possibly
|
||||
// a comment.
|
||||
type KV struct {
|
||||
Key string
|
||||
Value string
|
||||
Comment string
|
||||
hasEquals bool
|
||||
leadingSpace int // Space before the key. TODO handle spaces vs tabs.
|
||||
position Position
|
||||
Key string
|
||||
Value string
|
||||
// Whitespace after the value but before any comment
|
||||
spaceAfterValue string
|
||||
Comment string
|
||||
hasEquals bool
|
||||
leadingSpace int // Space before the key. TODO handle spaces vs tabs.
|
||||
position Position
|
||||
}
|
||||
|
||||
// Pos returns k's Position.
|
||||
@@ -581,8 +587,7 @@ func (k *KV) Pos() Position {
|
||||
return k.position
|
||||
}
|
||||
|
||||
// String prints k as it was parsed in the config file. There may be slight
|
||||
// changes to the whitespace between values.
|
||||
// String prints k as it was parsed in the config file.
|
||||
func (k *KV) String() string {
|
||||
if k == nil {
|
||||
return ""
|
||||
@@ -591,9 +596,9 @@ func (k *KV) String() string {
|
||||
if k.hasEquals {
|
||||
equals = " = "
|
||||
}
|
||||
line := fmt.Sprintf("%s%s%s%s", strings.Repeat(" ", int(k.leadingSpace)), k.Key, equals, k.Value)
|
||||
line := strings.Repeat(" ", int(k.leadingSpace)) + k.Key + equals + k.Value + k.spaceAfterValue
|
||||
if k.Comment != "" {
|
||||
line += " #" + k.Comment
|
||||
line += "#" + k.Comment
|
||||
}
|
||||
return line
|
||||
}
|
||||
|
||||
+7
-2
@@ -10,6 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func loadFile(t *testing.T, filename string) []byte {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -17,7 +18,11 @@ func loadFile(t *testing.T, filename string) []byte {
|
||||
return data
|
||||
}
|
||||
|
||||
var files = []string{"testdata/config1", "testdata/config2"}
|
||||
var files = []string{
|
||||
"testdata/config1",
|
||||
"testdata/config2",
|
||||
"testdata/eol-comments",
|
||||
}
|
||||
|
||||
func TestDecode(t *testing.T) {
|
||||
for _, filename := range files {
|
||||
@@ -28,7 +33,7 @@ func TestDecode(t *testing.T) {
|
||||
}
|
||||
out := cfg.String()
|
||||
if out != string(data) {
|
||||
t.Errorf("out != data: out: %q\ndata: %q", out, string(data))
|
||||
t.Errorf("%s out != data: got:\n%s\nwant:\n%s\n", filename, out, string(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package ssh_config
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type sshParser struct {
|
||||
@@ -122,11 +123,16 @@ func (p *sshParser) parseKV() sshParserStateFn {
|
||||
}
|
||||
patterns = append(patterns, pat)
|
||||
}
|
||||
// val.val at this point could be e.g. "example.com "
|
||||
hostval := strings.TrimRightFunc(val.val, unicode.IsSpace)
|
||||
spaceBeforeComment := val.val[len(hostval):]
|
||||
val.val = hostval
|
||||
p.config.Hosts = append(p.config.Hosts, &Host{
|
||||
Patterns: patterns,
|
||||
Nodes: make([]Node, 0),
|
||||
EOLComment: comment,
|
||||
hasEquals: hasEquals,
|
||||
Patterns: patterns,
|
||||
Nodes: make([]Node, 0),
|
||||
EOLComment: comment,
|
||||
spaceBeforeComment: spaceBeforeComment,
|
||||
hasEquals: hasEquals,
|
||||
})
|
||||
return p.parseStart
|
||||
}
|
||||
@@ -144,13 +150,16 @@ func (p *sshParser) parseKV() sshParserStateFn {
|
||||
lastHost.Nodes = append(lastHost.Nodes, inc)
|
||||
return p.parseStart
|
||||
}
|
||||
shortval := strings.TrimRightFunc(val.val, unicode.IsSpace)
|
||||
spaceAfterValue := val.val[len(shortval):]
|
||||
kv := &KV{
|
||||
Key: key.val,
|
||||
Value: val.val,
|
||||
Comment: comment,
|
||||
hasEquals: hasEquals,
|
||||
leadingSpace: key.Position.Col - 1,
|
||||
position: key.Position,
|
||||
Key: key.val,
|
||||
Value: shortval,
|
||||
spaceAfterValue: spaceAfterValue,
|
||||
Comment: comment,
|
||||
hasEquals: hasEquals,
|
||||
leadingSpace: key.Position.Col - 1,
|
||||
position: key.Position,
|
||||
}
|
||||
lastHost.Nodes = append(lastHost.Nodes, kv)
|
||||
return p.parseStart
|
||||
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
Host example # this comment terminates a Host line
|
||||
HostName example.com # aligned eol comment 1
|
||||
ForwardX11Timeout 52w # aligned eol comment 2
|
||||
# This comment takes up a whole line
|
||||
# This comment is offset and takes up a whole line
|
||||
AddressFamily inet # aligned eol comment 3
|
||||
Port 4242 #compact comment
|
||||
Reference in New Issue
Block a user