From 722517c4803cd34ca450b445e1acd8cc26a63374 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Thu, 3 May 2018 20:47:38 +0200 Subject: [PATCH] wip --- internal/ui/config/config.go | 76 +++++++++++++++++ internal/ui/config/config_test.go | 83 +++++++++++++++++++ internal/ui/config/testdata/quiet.conf | 1 + internal/ui/config/testdata/quiet.golden | 4 + internal/ui/config/testdata/repo_local.conf | 10 +++ internal/ui/config/testdata/repo_local.golden | 9 ++ 6 files changed, 183 insertions(+) create mode 100644 internal/ui/config/config.go create mode 100644 internal/ui/config/config_test.go create mode 100644 internal/ui/config/testdata/quiet.conf create mode 100644 internal/ui/config/testdata/quiet.golden create mode 100644 internal/ui/config/testdata/repo_local.conf create mode 100644 internal/ui/config/testdata/repo_local.golden diff --git a/internal/ui/config/config.go b/internal/ui/config/config.go new file mode 100644 index 000000000..33df5ca1b --- /dev/null +++ b/internal/ui/config/config.go @@ -0,0 +1,76 @@ +package config + +import ( + "fmt" + "reflect" + + "github.com/davecgh/go-spew/spew" + "github.com/hashicorp/hcl" + "github.com/hashicorp/hcl/hcl/ast" + "github.com/hashicorp/hcl/hcl/token" + "github.com/restic/restic/internal/errors" +) + +// Repo is a configured repository +type Repo struct { + Backend string + Path string +} + +// Config contains configuration items read from a file. +type Config struct { + Quiet bool `hcl:"quiet"` + Repos map[string]Repo `hcl:"repo"` +} + +// listTags returns the all the top-level tags with the name tagname of obj. +func listTags(obj interface{}, tagname string) map[string]struct{} { + list := make(map[string]struct{}) + + // resolve indirection if obj is a pointer + v := reflect.Indirect(reflect.ValueOf(obj)) + + for i := 0; i < v.NumField(); i++ { + f := v.Type().Field(i) + + val := f.Tag.Get(tagname) + list[val] = struct{}{} + } + + return list +} + +// Parse parses a config file from buf. +func Parse(buf []byte) (cfg Config, err error) { + parsed, err := hcl.ParseBytes(buf) + if err != nil { + return Config{}, err + } + + err = hcl.DecodeObject(&cfg, parsed) + if err != nil { + return Config{}, err + } + + // check for additional top-level items + validNames := listTags(cfg, "hcl") + for _, item := range parsed.Node.(*ast.ObjectList).Items { + fmt.Printf("-----------\n") + spew.Dump(item) + var ident string + for _, key := range item.Keys { + if key.Token.Type == token.IDENT { + ident = key.Token.Text + } + } + fmt.Printf("ident is %q\n", ident) + + if _, ok := validNames[ident]; !ok { + return Config{}, errors.Errorf("unknown option %q found at line %v, column %v: %v", + ident, item.Pos().Line, item.Pos().Column) + } + } + // spew.Dump(cfg) + + return cfg, nil +} diff --git a/internal/ui/config/config_test.go b/internal/ui/config/config_test.go new file mode 100644 index 000000000..43f8123cc --- /dev/null +++ b/internal/ui/config/config_test.go @@ -0,0 +1,83 @@ +package config + +import ( + "encoding/json" + "flag" + "io/ioutil" + "path/filepath" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" +) + +var updateGoldenFiles = flag.Bool("update", false, "update golden files in testdata/") + +func readTestFile(t testing.TB, filename string) []byte { + data, err := ioutil.ReadFile(filepath.Join("testdata", filename)) + if err != nil { + t.Fatal(err) + } + return data +} + +func saveGoldenFile(t testing.TB, base string, cfg Config) { + buf, err := json.MarshalIndent(cfg, "", " ") + if err != nil { + t.Fatalf("error marshaling result: %v", err) + } + buf = append(buf, '\n') + + if err = ioutil.WriteFile(filepath.Join("testdata", base+".golden"), buf, 0644); err != nil { + t.Fatalf("unable to update golden file: %v", err) + } +} + +func loadGoldenFile(t testing.TB, base string) Config { + buf, err := ioutil.ReadFile(filepath.Join("testdata", base+".golden")) + if err != nil { + t.Fatal(err) + } + + var cfg Config + err = json.Unmarshal(buf, &cfg) + if err != nil { + t.Fatal(err) + } + + return cfg +} + +func TestRead(t *testing.T) { + entries, err := ioutil.ReadDir("testdata") + if err != nil { + t.Fatal(err) + } + + for _, entry := range entries { + filename := entry.Name() + if filepath.Ext(filename) != ".conf" { + continue + } + + base := strings.TrimSuffix(filename, ".conf") + t.Run(base, func(t *testing.T) { + buf := readTestFile(t, filename) + + cfg, err := Parse(buf) + if err != nil { + t.Fatal(err) + } + + if *updateGoldenFiles { + saveGoldenFile(t, base, cfg) + } + + want := loadGoldenFile(t, base) + + if !cmp.Equal(want, cfg) { + t.Errorf("wrong config: %v", cmp.Diff(want, cfg)) + } + }) + } +} diff --git a/internal/ui/config/testdata/quiet.conf b/internal/ui/config/testdata/quiet.conf new file mode 100644 index 000000000..986554250 --- /dev/null +++ b/internal/ui/config/testdata/quiet.conf @@ -0,0 +1 @@ +quiet = true diff --git a/internal/ui/config/testdata/quiet.golden b/internal/ui/config/testdata/quiet.golden new file mode 100644 index 000000000..015f7223a --- /dev/null +++ b/internal/ui/config/testdata/quiet.golden @@ -0,0 +1,4 @@ +{ + "Quiet": true, + "Repos": null +} diff --git a/internal/ui/config/testdata/repo_local.conf b/internal/ui/config/testdata/repo_local.conf new file mode 100644 index 000000000..30230564c --- /dev/null +++ b/internal/ui/config/testdata/repo_local.conf @@ -0,0 +1,10 @@ +repo "test" { + backend = "local" + path = "/foo/bar/baz" +} + +foobar "test" { + x = "y" +} + +quiet = false diff --git a/internal/ui/config/testdata/repo_local.golden b/internal/ui/config/testdata/repo_local.golden new file mode 100644 index 000000000..6136f7a5a --- /dev/null +++ b/internal/ui/config/testdata/repo_local.golden @@ -0,0 +1,9 @@ +{ + "Quiet": false, + "Repos": { + "test": { + "Backend": "local", + "Path": "/foo/bar/baz" + } + } +}