diff --git a/AUTHORS.txt b/AUTHORS.txt index d306efb..311aeb1 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -3,6 +3,7 @@ Dustin Spicuzza Eugene Terentev Kevin Burke Mark Nevill +Scott Lessans Sergey Lukjanov Wayne Ashley Berry santosh653 <70637961+santosh653@users.noreply.github.com> diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d32a3f5 --- /dev/null +++ b/CHANGELOG.md @@ -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. diff --git a/config.go b/config.go index a734e53..b9d24d2 100644 --- a/config.go +++ b/config.go @@ -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 } diff --git a/config_test.go b/config_test.go index 2bdc3af..0bd350f 100644 --- a/config_test.go +++ b/config_test.go @@ -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)) } } } diff --git a/parser.go b/parser.go index 36c4205..2b1e718 100644 --- a/parser.go +++ b/parser.go @@ -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 diff --git a/testdata/eol-comments b/testdata/eol-comments new file mode 100644 index 0000000..cf8376d --- /dev/null +++ b/testdata/eol-comments @@ -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