diff --git a/Makefile b/Makefile index 01ce953..574ed7e 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ clean: rm -rf binaries test: - ginkgo -r --nodes 1 --randomizeAllSpecs --randomizeSuites --race --trace + ginkgo -r --randomizeAllSpecs --randomizeSuites --failOnPending --trace --race --nodes=2 --compilers=2 gobin: go build -ldflags='-s -w -extldflags "-static"' -o ${GOPATH}/bin/ytbx cmd/ytbx/main.go @@ -38,10 +38,10 @@ gobin: build: binaries/ytbx-linux-amd64 binaries/ytbx-darwin-amd64 binaries/ytbx-windows-amd64 binaries/ytbx-linux-amd64: $(sources) - GOOS=linux GOARCH=amd64 go build -ldflags='-s -w -extldflags "-static" -X github.com/HeavyWombat/ytbx/internal/cmd.version=$(version)' -o binaries/ytbx-linux-amd64 cmd/ytbx/main.go + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -tags netgo -ldflags='-s -w -extldflags "-static" -X github.com/HeavyWombat/ytbx/internal/cmd.version=$(version)' -o binaries/ytbx-linux-amd64 cmd/ytbx/main.go binaries/ytbx-darwin-amd64: $(sources) - GOOS=darwin GOARCH=amd64 go build -ldflags='-s -w -extldflags "-static" -X github.com/HeavyWombat/ytbx/internal/cmd.version=$(version)' -o binaries/ytbx-darwin-amd64 cmd/ytbx/main.go + CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -a -tags netgo -ldflags='-s -w -extldflags "-static" -X github.com/HeavyWombat/ytbx/internal/cmd.version=$(version)' -o binaries/ytbx-darwin-amd64 cmd/ytbx/main.go binaries/ytbx-windows-amd64: $(sources) - GOOS=windows GOARCH=amd64 go build -ldflags='-s -w -extldflags "-static" -X github.com/HeavyWombat/ytbx/internal/cmd.version=$(version)' -o binaries/ytbx-windows-amd64 cmd/ytbx/main.go + CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -a -tags netgo -ldflags='-s -w -extldflags "-static" -X github.com/HeavyWombat/ytbx/internal/cmd.version=$(version)' -o binaries/ytbx-windows-amd64 cmd/ytbx/main.go diff --git a/assets/examples/sample.toml b/assets/examples/sample.toml new file mode 100644 index 0000000..21895a2 --- /dev/null +++ b/assets/examples/sample.toml @@ -0,0 +1,33 @@ +# This is a TOML document. + +title = "TOML Example" + +[owner] +name = "Tom Preston-Werner" +dob = 1979-05-27T07:32:00-08:00 # First class dates + +[database] +server = "192.168.1.1" +ports = [ 8001, 8001, 8002 ] +connection_max = 5000 +enabled = true + +[servers] + + # Indentation (tabs and/or spaces) is allowed but not required + [servers.alpha] + ip = "10.0.0.1" + dc = "eqdc10" + + [servers.beta] + ip = "10.0.0.2" + dc = "eqdc10" + +[clients] +data = [ ["gamma", "delta"], [1, 2] ] + +# Line breaks are OK when inside arrays +hosts = [ + "alpha", + "omega" +] diff --git a/internal/cmd/get.go b/internal/cmd/get.go index 18c9e49..6417fe2 100644 --- a/internal/cmd/get.go +++ b/internal/cmd/get.go @@ -30,7 +30,7 @@ import ( // getCmd represents the get command var getCmd = &cobra.Command{ - Use: "get", + Use: "get ", Args: cobra.ExactArgs(2), Short: "Get the value at a given path", Long: "Get the value at a given path in the file.\n" + getPathHelp(), @@ -45,9 +45,9 @@ var getCmd = &cobra.Command{ boldKeys := true useIndentLines := true - outoutProcessor := neat.NewOutputProcessor(useIndentLines, boldKeys, &neat.DefaultColorSchema) + outputProcessor := neat.NewOutputProcessor(useIndentLines, boldKeys, &neat.DefaultColorSchema) - output, err := outoutProcessor.ToYAML(obj) + output, err := outputProcessor.ToYAML(obj) if err != nil { exitWithError("Failed to render gathered data", err) } diff --git a/pkg/v1/ytbx/getting_test.go b/pkg/v1/ytbx/getting_test.go index 818ba63..48707bc 100644 --- a/pkg/v1/ytbx/getting_test.go +++ b/pkg/v1/ytbx/getting_test.go @@ -29,33 +29,28 @@ var _ = Describe("getting stuff test cases", func() { Context("Grabing values by path", func() { It("should return the value referenced by the path", func() { example := yml("../../../assets/examples/types.yml") - Expect(example).ToNot(BeNil()) - Expect(grab(example, "/yaml/map/before")).To(BeEquivalentTo("after")) Expect(grab(example, "/yaml/map/intA")).To(BeEquivalentTo(42)) Expect(grab(example, "/yaml/map/mapA")).To(BeEquivalentTo(yml(`{ key0: A, key1: A }`))) Expect(grab(example, "/yaml/map/listA")).To(BeEquivalentTo(list(`[ A, A, A ]`))) - Expect(grab(example, "/yaml/named-entry-list-using-name/name=B")).To(BeEquivalentTo(yml(`{ name: B }`))) Expect(grab(example, "/yaml/named-entry-list-using-key/key=B")).To(BeEquivalentTo(yml(`{ key: B }`))) Expect(grab(example, "/yaml/named-entry-list-using-id/id=B")).To(BeEquivalentTo(yml(`{ id: B }`))) - Expect(grab(example, "/yaml/simple-list/1")).To(BeEquivalentTo("B")) Expect(grab(example, "/yaml/named-entry-list-using-key/3")).To(BeEquivalentTo(yml(`{ key: X }`))) - // --- --- --- - example = yml("../../../assets/bosh-yaml/manifest.yml") - Expect(example).ToNot(BeNil()) - Expect(grab(example, "/instance_groups/name=web/networks/name=concourse/static_ips/0")).To(BeEquivalentTo("XX.XX.XX.XX")) Expect(grab(example, "/instance_groups/name=worker/jobs/name=baggageclaim/properties")).To(BeEquivalentTo(yml(`{}`))) }) - It("should return the value referenced by the path", func() { + It("should return the whole tree if root is referenced", func() { example := yml("../../../assets/examples/types.yml") - Expect(example).ToNot(BeNil()) + Expect(grab(example, "/")).To(BeEquivalentTo(example)) + }) + It("should return useful error messages", func() { + example := yml("../../../assets/examples/types.yml") Expect(grabError(example, "/yaml/simple-list/-1")).To(BeEquivalentTo("failed to traverse tree, provided list index -1 is not in range: 0..4")) Expect(grabError(example, "/yaml/does-not-exist")).To(BeEquivalentTo("no key 'does-not-exist' found in map, available keys: map, simple-list, named-entry-list-using-name, named-entry-list-using-key, named-entry-list-using-id")) Expect(grabError(example, "/yaml/0")).To(BeEquivalentTo("failed to traverse tree, expected a list but found type map at /yaml")) diff --git a/pkg/v1/ytbx/input.go b/pkg/v1/ytbx/input.go index 8b00993..c7a5133 100644 --- a/pkg/v1/ytbx/input.go +++ b/pkg/v1/ytbx/input.go @@ -35,9 +35,10 @@ import ( "github.com/HeavyWombat/gonvenience/pkg/v1/bunt" "github.com/pkg/errors" - yaml "gopkg.in/yaml.v2" + toml "github.com/BurntSushi/toml" ordered "github.com/virtuald/go-ordered-json" + yaml "gopkg.in/yaml.v2" ) // PreserveKeyOrderInJSON specifies whether a special library is used to decode @@ -163,7 +164,8 @@ func LoadFiles(locationA string, locationB string) (InputFile, InputFile, error) return from.result, to.result, nil } -// LoadFile processes the provided input location to load a YAML (or JSON, or raw text) +// LoadFile processes the provided input location to load it as one of the +// supported document formats, or plain text if nothing else works. func LoadFile(location string) (InputFile, error) { var ( documents []interface{} @@ -182,10 +184,20 @@ func LoadFile(location string) (InputFile, error) { return InputFile{Location: location, Documents: documents}, nil } -// LoadDocuments reads the provided input data slice as a YAML or JSON file with -// potential multiple documents. It only acts as a dispatcher and depending on -// the input will either use `LoadJSONDocuments` or `LoadYAMLDocuments`. +// LoadDocuments reads the provided input data slice as a YAML, JSON, or TOML +// file with potential multiple documents. It only acts as a dispatcher and +// depending on the input will either use `LoadTOMLDocuments`, +// `LoadJSONDocuments`, or `LoadYAMLDocuments`. func LoadDocuments(input []byte) ([]interface{}, error) { + // There is no easy check whether the input data is TOML format, this is + // why there is currently no other option than simply trying to parse it. + if toml, err := LoadTOMLDocuments(input); err == nil { + return toml, err + } + + // In case the input data set starts with either a map or list start + // symbol, it is assumed to be a JSON document. In any other case, use + // the YAML parser function which also covers plain text documents. switch input[0] { case '{', '[': return LoadJSONDocuments(input) @@ -314,6 +326,21 @@ func LoadYAMLDocuments(input []byte) ([]interface{}, error) { return values, nil } +// LoadTOMLDocuments reads the provided input data slice as a TOML file, which +// can only have one document. For the sake of having similiar sounding +// functions and the same signatures, the function uses the plural in its name +// and returns a list of results even though it will only contain one entry. +// All map entries inside the result document are converted into YAML MapSlice +// types to make it compatible with the rest of the package. +func LoadTOMLDocuments(input []byte) ([]interface{}, error) { + var data interface{} + if err := toml.Unmarshal(input, &data); err != nil { + return nil, err + } + + return []interface{}{mapSlicify(data)}, nil +} + // mapSlicify makes sure that each occurrence of a map in the provided structure // is changed to a YAML MapSlice. // diff --git a/pkg/v1/ytbx/path.go b/pkg/v1/ytbx/path.go index 87b8cf5..4b1f4b1 100644 --- a/pkg/v1/ytbx/path.go +++ b/pkg/v1/ytbx/path.go @@ -186,8 +186,12 @@ func traverseTree(path Path, obj interface{}, leafFunc func(path Path, value int // ParseGoPatchStylePathString returns a path by parsing a string representation // which is assumed to be a GoPatch style path. func ParseGoPatchStylePathString(path string) (Path, error) { - elements := make([]PathElement, 0) + // Special case for root path + if path == "/" { + return Path{DocumentIdx: 0, PathElements: nil}, nil + } + elements := make([]PathElement, 0) for i, section := range strings.Split(path, "/") { if i == 0 { continue @@ -289,14 +293,9 @@ func ParseDotStylePathString(path string, obj interface{}) (Path, error) { // ParsePathString returns a path by parsing a string representation // of a path, which can be one of the supported types. func ParsePathString(pathString string, obj interface{}) (Path, error) { - if IsDotStylePath(pathString) { - return ParseDotStylePathString(pathString, obj) + if strings.HasPrefix(pathString, "/") { + return ParseGoPatchStylePathString(pathString) } - return ParseGoPatchStylePathString(pathString) -} - -// IsDotStylePath checks whether the path string is a Dot-Style path. -func IsDotStylePath(pathString string) bool { - return !strings.HasPrefix(pathString, "/") + return ParseDotStylePathString(pathString, obj) } diff --git a/pkg/v1/ytbx/path_test.go b/pkg/v1/ytbx/path_test.go index 84e065f..92f5b73 100644 --- a/pkg/v1/ytbx/path_test.go +++ b/pkg/v1/ytbx/path_test.go @@ -116,5 +116,11 @@ var _ = Describe("path tests", func() { {Idx: 1}, }})) }) + + It("should parse an input string that points to the root of the tree structure", func() { + path, err := ParseGoPatchStylePathString("/") + Expect(err).To(BeNil()) + Expect(path).To(BeEquivalentTo(Path{DocumentIdx: 0, PathElements: nil})) + }) }) })