From 7897293c53a37109841220de24d94fcd5501b87b Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Thu, 25 May 2017 08:00:10 -0700 Subject: [PATCH] Cleanup for public release - Test Include string representation - Add docs and examples --- config.go | 56 +++++++++++++++++++++++++++++++----------------- config_test.go | 19 ++++++++++++++++ example_test.go | 10 +++++++++ parser.go | 2 +- testdata/include | 6 +++--- 5 files changed, 69 insertions(+), 24 deletions(-) diff --git a/config.go b/config.go index 5646ce5..f6a04ec 100644 --- a/config.go +++ b/config.go @@ -297,20 +297,21 @@ func special(b byte) bool { return bytes.IndexByte(specialBytes, b) >= 0 } -// NewPattern creates a new Pattern for matching hosts. +// NewPattern creates a new Pattern for matching hosts. NewPattern("*") creates +// a Pattern that matches all hosts. +// +// From the manpage, a pattern consists of zero or more non-whitespace +// characters, `*' (a wildcard that matches zero or more characters), or `?' (a +// wildcard that matches exactly one character). For example, to specify a set +// of declarations for any host in the ".co.uk" set of domains, the following +// pattern could be used: +// +// Host *.co.uk +// +// The following pattern would match any host in the 192.168.0.[0-9] network range: +// +// Host 192.168.0.? func NewPattern(s string) (*Pattern, error) { - // From the manpage: - // A pattern consists of zero or more non-whitespace characters, - // `*' (a wildcard that matches zero or more characters), - // or `?' (a wildcard that matches exactly one character). - // For example, to specify a set of declarations for any host in the - // ".co.uk" set of domains, the following pattern could be used: - // - // Host *.co.uk - // - // The following pattern would match any host in the 192.168.0.[0-9] network range: - // - // Host 192.168.0.? if s == "" { return nil, errors.New("ssh_config: empty pattern") } @@ -344,6 +345,7 @@ func NewPattern(s string) (*Pattern, error) { return &Pattern{str: s, regex: r, not: negated}, nil } +// Host describes a Host directive and the keywords that follow it. type Host struct { // A list of host patterns that should match this host. Patterns []*Pattern @@ -492,6 +494,7 @@ type Include struct { leadingSpace uint16 position Position depth uint8 + hasEquals bool } const maxRecurseDepth = 5 @@ -519,15 +522,18 @@ func removeDups(arr []string) []string { // Configuration files are parsed greedily (e.g. as soon as this function runs). // Any error encountered while parsing nested configuration files will be // returned. -func NewInclude(directives []string, comment string, system bool, depth uint8) (*Include, error) { +func NewInclude(directives []string, hasEquals bool, pos Position, comment string, system bool, depth uint8) (*Include, error) { if depth > maxRecurseDepth { return nil, ErrDepthExceeded } inc := &Include{ - Comment: comment, - directives: directives, - files: make(map[string]*Config), - depth: depth, + Comment: comment, + directives: directives, + files: make(map[string]*Config), + position: pos, + leadingSpace: uint16(pos.Col) - 1, + depth: depth, + hasEquals: hasEquals, } // no need for inc.mu.Lock() since nothing else can access this inc matches := make([]string, 0) @@ -582,8 +588,18 @@ func (inc *Include) Get(alias, key string) string { return "" } -func (i *Include) String() string { - return "TODO" +// String prints out a string representation of this Include directive. Note +// included Config files are not printed as part of this representation. +func (inc *Include) String() string { + equals := " " + if inc.hasEquals { + equals = " = " + } + line := fmt.Sprintf("%sInclude%s%s", strings.Repeat(" ", int(inc.leadingSpace)), equals, strings.Join(inc.directives, " ")) + if inc.Comment != "" { + line += " #" + inc.Comment + } + return line } var matchAll *Pattern diff --git a/config_test.go b/config_test.go index 66762c7..72e07c9 100644 --- a/config_test.go +++ b/config_test.go @@ -3,6 +3,7 @@ package ssh_config import ( "bytes" "io/ioutil" + "log" "os" "path/filepath" "strings" @@ -253,6 +254,24 @@ func TestIncludeRecursive(t *testing.T) { } } +func TestIncludeString(t *testing.T) { + if testing.Short() { + t.Skip("skipping fs write in short mode") + } + data, err := ioutil.ReadFile("testdata/include") + if err != nil { + log.Fatal(err) + } + c, err := Decode(bytes.NewReader(data)) + if err != nil { + t.Fatal(err) + } + s := c.String() + if s != string(data) { + t.Errorf("mismatch: got %q\nwant %q", s, string(data)) + } +} + var matchTests = []struct { in []string alias string diff --git a/example_test.go b/example_test.go index dac8c14..5cceaee 100644 --- a/example_test.go +++ b/example_test.go @@ -11,3 +11,13 @@ func ExampleHost_Matches() { // true // false } + +func ExamplePattern() { + pat, _ := NewPattern("*") + host := &Host{Patterns: []*Pattern{pat}} + fmt.Println(host.Matches("test.stage.example.com")) + fmt.Println(host.Matches("othersubdomain.any.any")) + // Output: + // true + // true +} diff --git a/parser.go b/parser.go index ac39520..feb1497 100644 --- a/parser.go +++ b/parser.go @@ -131,7 +131,7 @@ func (p *sshParser) parseKV() sshParserStateFn { } lastHost := p.config.Hosts[len(p.config.Hosts)-1] if strings.ToLower(key.val) == "include" { - inc, err := NewInclude(strings.Split(val.val, " "), comment, p.system, p.depth+1) + inc, err := NewInclude(strings.Split(val.val, " "), hasEquals, key.Position, comment, p.system, p.depth+1) if err == ErrDepthExceeded { p.raiseError(val, err) return nil diff --git a/testdata/include b/testdata/include index 0a711ca..ee238dd 100644 --- a/testdata/include +++ b/testdata/include @@ -1,4 +1,4 @@ Host kevinburke.ssh_config.test.example.com - # This file (or files) needs to be found in ~/.ssh or /etc/ssh, depending on - # the test. - Include kevinburke-ssh-config-*-file + # This file (or files) needs to be found in ~/.ssh or /etc/ssh, depending on + # the test. + Include kevinburke-ssh-config-*-file