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:
Scott Lessans
2022-03-31 10:57:24 -07:00
committed by Kevin Burke
parent 91cd224c7f
commit 9b1b4df300
6 changed files with 71 additions and 25 deletions
+1
View File
@@ -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>
+19
View File
@@ -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.
+18 -13
View File
@@ -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
View File
@@ -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))
}
}
}
+19 -10
View File
@@ -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
+7
View File
@@ -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