commit def2cbb89d97550eb3b2a77e577b3e243a5c43e3 Author: Jack Christensen Date: Sat Mar 7 12:57:42 2020 -0600 Initial commit diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..e176228 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: go + +go: + - 1.x + - tip + +matrix: + allow_failures: + - go: tip diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f1b4c28 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2020 Jack Christensen + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..051e9e0 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/jackc/pgservicefile + +go 1.14 + +require github.com/stretchr/testify v1.5.1 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a80206a --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/pgservicefile.go b/pgservicefile.go new file mode 100644 index 0000000..32c9e45 --- /dev/null +++ b/pgservicefile.go @@ -0,0 +1,79 @@ +// Package pgservicefile is a parser PostgreSQL service files (e.g. .pg_service.conf). +package pgservicefile + +import ( + "bufio" + "errors" + "fmt" + "io" + "os" + "strings" +) + +type Service struct { + Name string + Settings map[string]string +} + +type Servicefile struct { + Services []*Service + servicesByName map[string]*Service +} + +// GetService returns the named service. +func (sf *Servicefile) GetService(name string) (*Service, error) { + service, present := sf.servicesByName[name] + if !present { + return nil, errors.New("not found") + } + return service, nil +} + +// ReadServicefile reads the file at path and parses it into a Servicefile. +func ReadServicefile(path string) (*Servicefile, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + return ParseServicefile(f) +} + +// ParseServicefile reads r and parses it into a Servicefile. +func ParseServicefile(r io.Reader) (*Servicefile, error) { + servicefile := &Servicefile{} + + var service *Service + scanner := bufio.NewScanner(r) + lineNum := 0 + for scanner.Scan() { + lineNum += 1 + line := scanner.Text() + line = strings.TrimSpace(line) + + if line == "" || strings.HasPrefix(line, "#") { + // ignore comments and empty lines + } else if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { + service = &Service{Name: line[1 : len(line)-1], Settings: make(map[string]string)} + servicefile.Services = append(servicefile.Services, service) + } else { + parts := strings.SplitN(line, "=", 2) + if len(parts) != 2 { + fmt.Errorf("unable to parse line %d", lineNum) + } + + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + + service.Settings[key] = value + } + } + + servicefile.servicesByName = make(map[string]*Service, len(servicefile.Services)) + for _, service := range servicefile.Services { + servicefile.servicesByName[service.Name] = service + } + + return servicefile, scanner.Err() +} diff --git a/pgservicefile_test.go b/pgservicefile_test.go new file mode 100644 index 0000000..d071ac3 --- /dev/null +++ b/pgservicefile_test.go @@ -0,0 +1,53 @@ +package pgservicefile_test + +import ( + "bytes" + "testing" + + "github.com/jackc/pgservicefile" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseServicefile(t *testing.T) { + buf := bytes.NewBufferString(`# A comment +[abc] +host=abc.example.com +port=9999 +dbname=abcdb +user=abcuser +# Another comment + +[def] +host = def.example.com +dbname = defdb +user = defuser +application_name = has space +`) + + servicefile, err := pgservicefile.ParseServicefile(buf) + require.NoError(t, err) + require.NotNil(t, servicefile) + + assert.Len(t, servicefile.Services, 2) + assert.Equal(t, "abc", servicefile.Services[0].Name) + assert.Equal(t, "def", servicefile.Services[1].Name) + + abc, err := servicefile.GetService("abc") + require.NoError(t, err) + assert.Equal(t, servicefile.Services[0], abc) + assert.Len(t, abc.Settings, 4) + assert.Equal(t, "abc.example.com", abc.Settings["host"]) + assert.Equal(t, "9999", abc.Settings["port"]) + assert.Equal(t, "abcdb", abc.Settings["dbname"]) + assert.Equal(t, "abcuser", abc.Settings["user"]) + + def, err := servicefile.GetService("def") + require.NoError(t, err) + assert.Equal(t, servicefile.Services[1], def) + assert.Len(t, def.Settings, 4) + assert.Equal(t, "def.example.com", def.Settings["host"]) + assert.Equal(t, "defdb", def.Settings["dbname"]) + assert.Equal(t, "defuser", def.Settings["user"]) + assert.Equal(t, "has space", def.Settings["application_name"]) +}