pax_global_header00006660000000000000000000000064141755113750014523gustar00rootroot0000000000000052 comment=148d4fa8111c22f425261645f83a663d7c334650 ytbx-1.4.4/000077500000000000000000000000001417551137500125175ustar00rootroot00000000000000ytbx-1.4.4/.github/000077500000000000000000000000001417551137500140575ustar00rootroot00000000000000ytbx-1.4.4/.github/dependabot.yml000066400000000000000000000001771417551137500167140ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: gomod directory: "/" schedule: interval: daily open-pull-requests-limit: 10 ytbx-1.4.4/.github/workflows/000077500000000000000000000000001417551137500161145ustar00rootroot00000000000000ytbx-1.4.4/.github/workflows/build.yml000066400000000000000000000032411417551137500177360ustar00rootroot00000000000000--- name: Build and Tests on: push: tags-ignore: - '**' branches: - main pull_request: branches: - main jobs: build: name: Build and Tests runs-on: ubuntu-latest steps: - name: Set up Go uses: actions/setup-go@v2 with: go-version: 1.17.x - name: Check out code into the Go module directory uses: actions/checkout@v2 - name: Verify Go Modules Setup run: | go mod verify - name: Install Tools run: | pushd "$(mktemp -d)" go get github.com/gordonklaus/ineffassign go get golang.org/x/lint/golint go get github.com/client9/misspell/cmd/misspell go get honnef.co/go/tools/cmd/staticcheck go get github.com/onsi/ginkgo/ginkgo github.com/onsi/gomega/... popd - name: Build Go Code run: | go build ./... - name: Sanity Check (go vet) run: | go vet ./... - name: Sanity Check (ineffassign) run: | go get github.com/gordonklaus/ineffassign ineffassign ./... - name: Sanity Check (golint) run: | golint ./... - name: Sanity Check (misspell) run: | find . -type f | xargs misspell -source=text -error - name: Sanity Check (staticcheck) run: | staticcheck ./... - name: Run Go Unit Tests run: | ginkgo -randomizeAllSpecs -randomizeSuites -failOnPending -nodes=1 -compilers=1 -race -trace -cover - name: Upload Code Coverage Profile uses: codecov/codecov-action@v1 with: files: ./*.coverprofile flags: unittests fail_ci_if_error: true verbose: false ytbx-1.4.4/.gitignore000066400000000000000000000000171417551137500145050ustar00rootroot00000000000000*.coverprofile ytbx-1.4.4/.grenrc.yml000066400000000000000000000002311417551137500145740ustar00rootroot00000000000000--- dataSource: "commits" prefix: "ytbx release " includeMessages: "commits" changelogFilename: "CHANGELOG.md" template: commit: "{{url}} {{message}}" ytbx-1.4.4/LICENSE000066400000000000000000000020621417551137500135240ustar00rootroot00000000000000MIT License Copyright (c) 2018 The Homeport Team 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. ytbx-1.4.4/README.md000066400000000000000000000017341417551137500140030ustar00rootroot00000000000000# ytbx [![License](https://img.shields.io/github/license/gonvenience/ytbx.svg)](https://github.com/gonvenience/ytbx/blob/main/LICENSE) [![Go Report Card](https://goreportcard.com/badge/github.com/gonvenience/ytbx)](https://goreportcard.com/report/github.com/gonvenience/ytbx) [![Build and Tests](https://github.com/gonvenience/ytbx/workflows/Build%20and%20Tests/badge.svg)](https://github.com/gonvenience/ytbx/actions?query=workflow%3A%22Build+and+Tests%22) [![Codecov](https://img.shields.io/codecov/c/github/gonvenience/ytbx/main.svg)](https://codecov.io/gh/gonvenience/ytbx) [![PkgGoDev](https://pkg.go.dev/badge/github.com/gonvenience/ytbx)](https://pkg.go.dev/github.com/gonvenience/ytbx) [![Release](https://img.shields.io/github/release/gonvenience/ytbx.svg)](https://github.com/gonvenience/ytbx/releases/latest) ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/gonvenience/ytbx) Golang package with convenience functions to load and process YAML files ytbx-1.4.4/assets/000077500000000000000000000000001417551137500140215ustar00rootroot00000000000000ytbx-1.4.4/assets/bosh-yaml/000077500000000000000000000000001417551137500157145ustar00rootroot00000000000000ytbx-1.4.4/assets/bosh-yaml/manifest.yml000066400000000000000000000074161417551137500202550ustar00rootroot00000000000000--- name: concourse director_uuid: YOUR-BOSH-DIRECTOR-UUID-GOES-HERE # REPLACE THIS VALUE WITH YOUR DIRECTOR UUID releases: - name: concourse version: latest - name: garden-runc version: latest instance_groups: - name: web instances: 1 resource_pool: concourse_resource_pool networks: - name: concourse static_ips: [XX.XX.XX.XX] # ADD THE STATIC IP ADDRESSES FOR YOUR web INSTACES HERE IF APPLICABLE, OTHERWISE DELETE THIS SECTION jobs: - release: concourse name: atc properties: postgresql_database: &atc-db atc external_url: http://XX.XX.XX.XX:8080 # ADD YOUR web INSTANCE(s) HOSTNAME OR IP ADDRESS HERE development_mode: true # FOR NON-DEV MODE, CHANGE THIS TO FALSE AND REFER TO CONCOURSE DOCUMENTATION FOR SECURITY CONFIGURATION SETTINGS - release: concourse name: tsa properties: {} - name: db instances: 1 resource_pool: concourse_resource_pool networks: [{name: concourse}] persistent_disk: 10240 jobs: - release: concourse name: postgresql properties: databases: - name: *atc-db role: atc password: YOUR-ATC-DB-PASSWORD-GOES-HERE # UPDATED WITH YOUR CUSTOM atc db PASSWORD HERE - name: worker instances: 1 resource_pool: concourse_resource_pool networks: [{name: concourse}] jobs: - release: concourse name: groundcrew properties: http_proxy_url: http://XX.XX.XX.XX:8080 # IF PROXY CONFIG FOR THE workers EXISTS, SETUP IT HERE. OTHERWISE REMOVE IT https_proxy_url: http://XX.XX.XX.XX:8080 # IF PROXY CONFIG FOR THE workers EXISTS, SETUP IT HERE. OTHERWISE REMOVE IT no_proxy: [localhost,127.0.0.1] # IF PROXY CONFIG FOR THE workers EXISTS, you may add here a list domains and IPs with optional port for which the proxy should be bypassed - release: concourse name: baggageclaim properties: {} - release: garden-runc name: garden properties: garden: listen_network: tcp listen_address: 0.0.0.0:7777 networks: - name: concourse subnets: - range: 10.219.56.0/23 # REPLACE THIS WITH YOUR SUBNET IP RANGE gateway: # ADD YOUR GATEWAY IP ADDRESS HERE dns: - # ADD YOUR DNS IP ADDRESS HERE static: - XX.XX.XX.XX # ADD STATIC IP ADDRESSES FOR CONCOURSE VMS IF APPLICABLE e.g. web instance(s). IF NONE, THEN REMOVE THIS SECTION reserved: - 10.222.56.0-10.222.56.255 # REPLACE THIS ENTRY WITH YOUR RESERVED IP ADDRESSES FOR THIS NETWORK IF APPLICABLE, OTHERWISE REMOVE THIS SECTION cloud_properties: name: D927_10_222_56_0_23 # IF APPLICABLE, REPLACE THIS ENTRY WITH THE NAME OF YOUR NETWORK e.g. vSphere subnet ID, OTHERWISE REMOVE THIS SECTION resource_pools: - name: concourse_resource_pool stemcell: name: bosh-vsphere-esxi-ubuntu-trusty-go_agent # UPDATE ACCORDINGLY WITH THE STEMCELL ID FOR THE TARGETED IaaS version: '3232.2' # UDPATE ACCORDINGLY WITH THE UPLOADED STEMCELL VERSION network: concourse cloud_properties: # UDPATE THIS SECTION ACCORDINGLY BASED ON YOUR VM DEFINITIONS AND CLUSTER INFO ram: 4096 disk: 32768 cpu: 2 datacenters: - clusters: - CLS_PAAS_SFT_035: {} compilation: reuse_compilation_vms: true workers: 3 network: concourse cloud_properties: # UDPATE THIS SECTION ACCORDINGLY BASED ON YOUR VM DEFINITIONS AND CLUSTER INFO ram: 4096 disk: 32768 cpu: 2 datacenters: - clusters: - CLS_PAAS_SFT_035: {} update: canaries: 1 max_in_flight: 3 serial: false canary_watch_time: 1000-60000 update_watch_time: 1000-60000 ytbx-1.4.4/assets/examples/000077500000000000000000000000001417551137500156375ustar00rootroot00000000000000ytbx-1.4.4/assets/examples/empty.yml000066400000000000000000000000031417551137500175110ustar00rootroot00000000000000---ytbx-1.4.4/assets/examples/sample.toml000066400000000000000000000010411417551137500200110ustar00rootroot00000000000000# 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" ] ytbx-1.4.4/assets/examples/types.yml000066400000000000000000000015551417551137500175340ustar00rootroot00000000000000--- yaml: map: before: after stringA: foobar stringB: fOObAr intA: 42 intB: 10 floatA: 3.1415 floatB: 2.71 boolA: true boolB: false mapA: { key0: A, key1: A } mapB: { key0: B, key1: B } listA: [ A, A, A ] listB: [ B, B, B ] type-change-1: string type-change-2: "12" whitespaces: "Strings can have whitespaces." simple-list: - A - B - C - X - Z named-entry-list-using-name: - name: A foo: bar - name: B foo: bar - name: C foo: bar - name: X foo: bar - name: Z foo: bar named-entry-list-using-key: - key: A foo: bar - key: B foo: bar - key: C foo: bar - key: X foo: bar - key: Z foo: bar named-entry-list-using-id: - id: A foo: bar - id: B foo: bar - id: C foo: bar - id: X foo: bar - id: Z foo: bar ytbx-1.4.4/assets/testbed/000077500000000000000000000000001417551137500154535ustar00rootroot00000000000000ytbx-1.4.4/assets/testbed/example.yml000066400000000000000000000002761417551137500176360ustar00rootroot00000000000000--- # structure with maps yaml: structure: somekey: foobar # structure with a list of maps list: - name: one somekey: foobar # structure with a simple list simpleList: - one - two ytbx-1.4.4/assets/testbed/sample_a.yml000066400000000000000000000003501417551137500177550ustar00rootroot00000000000000--- # structure with maps yaml: structure: somekey: foo dot: samevalue # structure with a list of maps list: - name: one somekey: foo - name: sametwo somekey: samekey # structure with a simple list simpleList: - one ytbx-1.4.4/assets/testbed/sample_b.yml000066400000000000000000000002661417551137500177640ustar00rootroot00000000000000--- # structure with maps yaml: structure: somekey: bar dot: samevalue # structure with a list of maps list: - name: two somekey: bar - name: sametwo somekey: samekey ytbx-1.4.4/common.go000066400000000000000000000037741417551137500143510ustar00rootroot00000000000000// Copyright © 2018 The Homeport Team // // 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. package ytbx import ( "reflect" yamlv3 "gopkg.in/yaml.v3" ) // Internal string constants for type names and type decisions const ( typeMap = "map" typeSimpleList = "list" typeComplexList = "complex-list" ) // GetType returns the type of the input value with a YAML specific view func GetType(value interface{}) string { switch tobj := value.(type) { case *yamlv3.Node: switch tobj.Kind { case yamlv3.MappingNode: return typeMap case yamlv3.SequenceNode: if hasMappingNodes(tobj) { return typeComplexList } return typeSimpleList default: return reflect.TypeOf(tobj.Value).Kind().String() } default: return reflect.TypeOf(value).Kind().String() } } func hasMappingNodes(sequenceNode *yamlv3.Node) bool { counter := 0 for _, entry := range sequenceNode.Content { if entry.Kind == yamlv3.MappingNode { counter++ } } return counter == len(sequenceNode.Content) } ytbx-1.4.4/convert.go000066400000000000000000000026121417551137500145270ustar00rootroot00000000000000// Copyright © 2018 The Homeport Team // // 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. package ytbx import ( yamlv3 "gopkg.in/yaml.v3" ) func asYAMLNode(obj interface{}) (*yamlv3.Node, error) { data, err := yamlv3.Marshal(obj) if err != nil { return nil, err } var node yamlv3.Node if err := yamlv3.Unmarshal(data, &node); err != nil { return nil, err } return &node, nil } ytbx-1.4.4/delete.go000066400000000000000000000060131417551137500143100ustar00rootroot00000000000000// Copyright © 2020 The Homeport Team // // 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. package ytbx import ( yamlv3 "gopkg.in/yaml.v3" ) // Delete removes the section identified by the path from the YAML structure func Delete(node *yamlv3.Node, pathString string) (*yamlv3.Node, error) { path, err := ParsePathString(pathString, node) if err != nil { return nil, err } switch node.Kind { case yamlv3.DocumentNode: return deletePath(node.Content[0], path) default: return deletePath(node, path) } } func deletePath(node *yamlv3.Node, path Path) (*yamlv3.Node, error) { parentPath := Path{ DocumentIdx: path.DocumentIdx, PathElements: path.PathElements[:len(path.PathElements)-1], } parent, err := grabByPath(node, parentPath) if err != nil { return nil, err } var ( lastPathElement = path.PathElements[len(path.PathElements)-1] deletedNode *yamlv3.Node = nil ) switch parent.Kind { case yamlv3.MappingNode: var deleteIdx int for i := 0; i < len(parent.Content); i += 2 { k, v := parent.Content[i], parent.Content[i+1] if k.Value == lastPathElement.Name { deleteIdx = i deletedNode = v break } } // delete the entry at delete index and the one after that as these two are // the key (first entry) and the value (second entry) parent.Content = append( parent.Content[:deleteIdx], parent.Content[deleteIdx+2:]..., ) return deletedNode, nil case yamlv3.SequenceNode: var deleteIdx int if lastPathElement.isSimpleListElement() { deleteIdx = lastPathElement.Idx } else { deleteIdx, err = getIndexByIdentifierAndName(parent, lastPathElement.Key, lastPathElement.Name) if err != nil { return nil, err } } deletedNode = parent.Content[deleteIdx] // delete the entry that was identified by the deletion index, since it is a // sequence (list), only one entry needs to be deleted parent.Content = append( parent.Content[:deleteIdx], parent.Content[deleteIdx+1:]..., ) return deletedNode, nil } return nil, nil } ytbx-1.4.4/delete_test.go000066400000000000000000000047611417551137500153570ustar00rootroot00000000000000// Copyright © 2020 The Homeport Team // // 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. package ytbx_test import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "gopkg.in/yaml.v3" "github.com/gonvenience/ytbx" ) var _ = Describe("Delete from YAML", func() { var example *yaml.Node BeforeEach(func() { example = yml(assets("examples", "types.yml")) }) Context("Delete path from given YAML structure", func() { It("should delete an entry in a map referenced by the path", func() { node, err := ytbx.Delete(example, "/yaml/map/before") Expect(err).ToNot(HaveOccurred()) Expect(node.Value).To(BeEquivalentTo("after")) Expect(ytbx.IsPathInTree(example, "/yaml/map/before")).To(BeFalse()) }) It("should delete an entry in a simple list referenced by the path", func() { node, err := ytbx.Delete(example, "/yaml/simple-list/1") Expect(err).ToNot(HaveOccurred()) Expect(node.Value).To(BeEquivalentTo("B")) list, err := ytbx.Grab(example, "/yaml/simple-list") Expect(err).ToNot(HaveOccurred()) Expect(len(list.Content)).To(Equal(4)) }) It("should delete an entry in a named entry list referenced by the path", func() { node, err := ytbx.Delete(example, "/yaml/named-entry-list-using-name/name=C") Expect(err).ToNot(HaveOccurred()) Expect(node).To(BeAsNode(yml(`{ name: C, foo: bar }`))) list, err := ytbx.Grab(example, "/yaml/named-entry-list-using-name") Expect(err).ToNot(HaveOccurred()) Expect(len(list.Content)).To(Equal(4)) }) }) }) ytbx-1.4.4/errors.go000066400000000000000000000047321417551137500143700ustar00rootroot00000000000000// Copyright © 2018 The Homeport Team // // 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. package ytbx import ( "fmt" "strings" ) // KeyNotFoundInMapError represents an error when a key in a map was expected, // but could not be found. type KeyNotFoundInMapError struct { MissingKey string AvailableKeys []string } func (e *KeyNotFoundInMapError) Error() string { return fmt.Sprintf("no key '%s' found in map, available keys: %s", e.MissingKey, strings.Join(e.AvailableKeys, ", ")) } // NoNamedEntryListError represents the situation where a list was expected to // be a named-entry list, but one or more entries were not maps. type NoNamedEntryListError struct { } func (e *NoNamedEntryListError) Error() string { return "not a named-entry list, one or more entries are not of type map" } // NewInvalidPathError creates a new InvalidPathString func NewInvalidPathError(style PathStyle, pathString string, format string, a ...interface{}) *InvalidPathString { return &InvalidPathString{ Style: style, PathString: pathString, Explanation: fmt.Sprintf(format, a...), } } // InvalidPathString represents the error that a path string is not a valid // Dot-style or GoPatch path syntax and does not match a provided document. type InvalidPathString struct { Style PathStyle PathString string Explanation string } func (e *InvalidPathString) Error() string { return fmt.Sprintf("invalid %v style path %s, %s", e.Style, e.PathString, e.Explanation) } ytbx-1.4.4/getting.go000066400000000000000000000071441417551137500145150ustar00rootroot00000000000000// Copyright © 2018 The Homeport Team // // 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. package ytbx import ( "fmt" yamlv3 "gopkg.in/yaml.v3" ) // Grab gets the value from the provided YAML tree using a path to traverse // through the tree structure func Grab(node *yamlv3.Node, pathString string) (*yamlv3.Node, error) { path, err := ParsePathString(pathString, node) if err != nil { return nil, err } switch node.Kind { case yamlv3.DocumentNode: return grabByPath(node.Content[0], path) default: return grabByPath(node, path) } } func grabByPath(node *yamlv3.Node, path Path) (*yamlv3.Node, error) { pointer := node pointerPath := Path{DocumentIdx: path.DocumentIdx} for _, element := range path.PathElements { switch { // Key/Value Map, where the element name is the key for the map case element.isMapElement(): if pointer.Kind != yamlv3.MappingNode { return nil, fmt.Errorf("failed to traverse tree, expected %s but found type %s at %s", typeMap, GetType(pointer), pointerPath.ToGoPatchStyle(), ) } entry, err := getValueByKey(pointer, element.Name) if err != nil { return nil, err } pointer = entry // Complex List, where each list entry is a Key/Value map and the entry is // identified by name using an identifier (e.g. name, key, or id) case element.isComplexListElement(): if pointer.Kind != yamlv3.SequenceNode { return nil, fmt.Errorf("failed to traverse tree, expected %s but found type %s at %s", typeComplexList, GetType(pointer), pointerPath.ToGoPatchStyle(), ) } entry, err := getEntryByIdentifierAndName(pointer, element.Key, element.Name) if err != nil { return nil, err } pointer = entry // Simple List (identified by index) case element.isSimpleListElement(): if pointer.Kind != yamlv3.SequenceNode { return nil, fmt.Errorf("failed to traverse tree, expected %s but found type %s at %s", typeSimpleList, GetType(pointer), pointerPath.ToGoPatchStyle(), ) } if element.Idx < 0 || element.Idx >= len(pointer.Content) { return nil, fmt.Errorf("failed to traverse tree, provided %s index %d is not in range: 0..%d", typeSimpleList, element.Idx, len(pointer.Content)-1, ) } pointer = pointer.Content[element.Idx] default: return nil, fmt.Errorf("failed to traverse tree, the provided path %s seems to be invalid", path) } // Update the path that the current pointer to keep track of the traversing pointerPath.PathElements = append(pointerPath.PathElements, element) } return pointer, nil } ytbx-1.4.4/getting_test.go000066400000000000000000000101741417551137500155510ustar00rootroot00000000000000// Copyright © 2018 The Homeport Team // // 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. package ytbx_test import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/gonvenience/ytbx" ) 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(grab(example, "/yaml/map/before")).To(BeEquivalentTo("after")) Expect(grab(example, "/yaml/map/intA")).To(BeEquivalentTo(42)) Expect(grab(example, "/yaml/map/mapA")).To(BeAsNode(yml(`{ key0: A, key1: A }`))) Expect(grab(example, "/yaml/map/listA")).To(BeAsNode(list(`[ A, A, A ]`))) Expect(grab(example, "/yaml/named-entry-list-using-name/name=B")).To(BeAsNode(yml(`{ name: B, foo: bar }`))) Expect(grab(example, "/yaml/named-entry-list-using-key/key=B")).To(BeAsNode(yml(`{ key: B, foo: bar }`))) Expect(grab(example, "/yaml/named-entry-list-using-id/id=B")).To(BeAsNode(yml(`{ id: B, foo: bar }`))) Expect(grab(example, "/yaml/simple-list/1")).To(BeEquivalentTo("B")) Expect(grab(example, "/yaml/named-entry-list-using-key/3")).To(BeAsNode(yml(`{ key: X, foo: bar }`))) example = yml(assets("bosh-yaml", "manifest.yml")) 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(BeAsNode(yml(`{}`))) }) It("should return the whole tree if root is referenced", func() { file, err := ytbx.LoadFile(assets("examples", "types.yml")) Expect(err).ToNot(HaveOccurred()) document := file.Documents[0] Expect(grab(document, "/")).To(BeAsNode(document.Content[0])) }) 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 list but found type map at /yaml")) Expect(grabError(example, "/yaml/simple-list/foobar")).To(BeEquivalentTo("failed to traverse tree, expected map but found type list at /yaml/simple-list")) Expect(grabError(example, "/yaml/map/foobar=0")).To(BeEquivalentTo("failed to traverse tree, expected complex-list but found type map at /yaml/map")) Expect(grabError(example, "/yaml/named-entry-list-using-id/id=0")).To(BeEquivalentTo("there is no entry id=0 in the list")) }) }) Context("Trying to get values by path in an empty file", func() { It("should return a not found key error", func() { emptyFile := yml(assets("examples", "empty.yml")) Expect(grabError(emptyFile, "/does-not-exist")).To( BeEquivalentTo("failed to traverse tree, expected map but found type string at /"), ) }) }) }) ytbx-1.4.4/go.mod000066400000000000000000000023261417551137500136300ustar00rootroot00000000000000module github.com/gonvenience/ytbx go 1.17 require ( github.com/BurntSushi/toml v1.0.0 github.com/gonvenience/bunt v1.3.3 github.com/gonvenience/neat v1.3.8 github.com/gonvenience/text v1.0.7 github.com/gonvenience/wrap v1.1.1 github.com/gorilla/mux v1.8.0 github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.18.1 github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b ) require ( github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/gonvenience/term v1.0.2 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/nxadm/tail v1.4.8 // indirect golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) ytbx-1.4.4/go.sum000066400000000000000000000427471417551137500136700ustar00rootroot00000000000000github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/gonvenience/bunt v1.1.3/go.mod h1:hZ898ZprNWgaVlq4s1ivsJu3AY+3wrlJadF5Gp7Yq2c= github.com/gonvenience/bunt v1.3.2 h1:gDiyTDfPf87fCtIbFzvENrmlnDZYbENdhhRW2kun/tw= github.com/gonvenience/bunt v1.3.2/go.mod h1:oTOZqb1TVL1KqZm57zUCJCiwUliNfY8+d3QndOVqxpg= github.com/gonvenience/bunt v1.3.3 h1:a751qSbJIgWGbazGYr9hyuudOg7wMHh2m4JjE3tfURE= github.com/gonvenience/bunt v1.3.3/go.mod h1:XjlODZ8sTL9jQs9c4Kj1ZBTrHSsbUGdoRy7ROCtw8nU= github.com/gonvenience/neat v1.3.7 h1:k4shy3sgSBfUk9LTN51naxVIkB6BlGaxH0ReERO92zw= github.com/gonvenience/neat v1.3.7/go.mod h1:Y4eeQH3GEBvjmLoMiu4oWGyOopGDaI2/y2jwcVfvlvs= github.com/gonvenience/neat v1.3.8 h1:Hk06QOUrhJjY439tk3S0GYgUStBNG5YmrAoND6PrT48= github.com/gonvenience/neat v1.3.8/go.mod h1:4SFVz5TfDxRwOWEijaQrR+3g0j5ZRyoYO3HeyNsfTlk= github.com/gonvenience/term v1.0.0/go.mod h1:wohD4Iqso9Eol7qc2VnNhSFFhZxok5PvO7pZhdrAn4E= github.com/gonvenience/term v1.0.1 h1:8bg2O0ox0Ss64nnUxW5AXlSHhllc8dTOSKuKu6uoGpw= github.com/gonvenience/term v1.0.1/go.mod h1:TrQEhxBNE/ng5kTV+S0OvQowTDJSfhlBeZbcOmTR6qI= github.com/gonvenience/term v1.0.2 h1:qKa2RydbWIrabGjR/fegJwpW5m+JvUwFL8mLhHzDXn0= github.com/gonvenience/term v1.0.2/go.mod h1:wThTR+3MzWtWn7XGVW6qQ65uaVf8GHED98KmwpuEQeo= github.com/gonvenience/text v1.0.6 h1:9JH9fz0BL0NX4uKmjLuVcsBKiniPa+XLpf8KH9so44U= github.com/gonvenience/text v1.0.6/go.mod h1:9U5WbkT/5wR5+aNMR4HucamY+HgVMEn+UbF78XHmUio= github.com/gonvenience/text v1.0.7 h1:YmIqmgTwxnACYCG59DykgMbomwteYyNhAmEUEJtPl14= github.com/gonvenience/text v1.0.7/go.mod h1:OAjH+mohRszffLY6OjgQcUXiSkbrIavooFpfIt1ZwAs= github.com/gonvenience/wrap v1.1.0 h1:d8gEZrXS/zg4BC1q0U4nHpPIh5k6muKpQ1+rQFBwpYc= github.com/gonvenience/wrap v1.1.0/go.mod h1:L47Cm1sK1G8QmFAYQfkHcF/sQ1IBJUa0u4sjqiLqPdM= github.com/gonvenience/wrap v1.1.1 h1:7cMuyMtTdUe2aObp760CzXo/wOmFaY7JXsIksu8NYYc= github.com/gonvenience/wrap v1.1.1/go.mod h1:u27bruNdIB6U5nnR0qq+GRRKbAJzEqC0H3DBHxl7YNM= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 h1:BXxTozrOU8zgC5dkpn3J6NTRdoP+hjok/e+ACr4Hibk= github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3/go.mod h1:x1uk6vxTiVuNt6S5R2UYgdhpj3oKojXvOXauHZ7dEnI= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= github.com/onsi/ginkgo v1.15.2/go.mod h1:Dd6YFfwBW84ETqqtL0CPyPXillHgY6XhQH3uuCCTr/o= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.0 h1:ngbYoRctxjl8SiF7XgP0NxBFbfHcg3wfHMMaFHWwMTM= github.com/onsi/gomega v1.18.0/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo= github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:RmMWU37GKR2s6pgrIEB4ixgpVCt/cf7dnJv3fuH1J1c= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210917163549-3c21e5b27794 h1:pOaRGvJk+MpHIfe37zcmbwolJplrAmLKmvggJVLkYl8= golang.org/x/net v0.0.0-20210917163549-3c21e5b27794/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba h1:6u6sik+bn/y7vILcYkK3iwTBWN7WtBvB0+SZswQnbf8= golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210916214954-140adaaadfaf h1:Ihq/mm/suC88gF8WFcVwk+OV6Tq+wyA1O0E5UEvDglI= golang.org/x/term v0.0.0-20210916214954-140adaaadfaf/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ytbx-1.4.4/input.go000066400000000000000000000240471417551137500142140ustar00rootroot00000000000000// Copyright © 2018 The Homeport Team // // 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. package ytbx import ( "bytes" "encoding/json" "fmt" "io" "io/ioutil" "net/http" "net/url" "os" "path/filepath" "sort" "strings" "github.com/BurntSushi/toml" "github.com/gonvenience/bunt" "github.com/gonvenience/text" "github.com/gonvenience/wrap" ordered "github.com/virtuald/go-ordered-json" yamlv3 "gopkg.in/yaml.v3" ) // PreserveKeyOrderInJSON specifies whether a special library is used to decode // JSON input to preserve the order of keys in maps even though that is not part // of the JSON specification. var PreserveKeyOrderInJSON = false // DecoderProxy can either be used with the standard JSON Decoder, or the // specialised JSON library fork that supports preserving key order type DecoderProxy struct { standard *json.Decoder ordered *ordered.Decoder } // InputFile represents the actual input file (local, or fetched remotely) that // needs to be processed. It can contain multiple documents, where a document // is a map or a list of things. type InputFile struct { Location string Note string Documents []*yamlv3.Node Names []string } // NewDecoderProxy creates a new decoder proxy which either works in ordered // mode or standard mode. func NewDecoderProxy(keepOrder bool, r io.Reader) *DecoderProxy { if keepOrder { decoder := ordered.NewDecoder(r) decoder.UseOrderedObject() return &DecoderProxy{ordered: decoder} } return &DecoderProxy{standard: json.NewDecoder(r)} } // Decode is a delegate function that calls JSON Decoder `Decode` func (d *DecoderProxy) Decode(v interface{}) error { if d.ordered != nil { return d.ordered.Decode(v) } return d.standard.Decode(v) } // HumanReadableLocationInformation create a nicely decorated information about // the provided input location. It will output the absolute path of the file // rather than the possibly relative location, or it will show the URL in the // usual look-and-feel of URIs. func HumanReadableLocationInformation(inputFile InputFile) string { var buf bytes.Buffer // Start with a nice location output buf.WriteString(HumanReadableLocation(inputFile.Location)) // Add additional note if it is set if inputFile.Note != "" { bunt.Fprintf(&buf, ", Orange{%s}", inputFile.Note) } // Add an information about how many documents are in the provided input file if documents := len(inputFile.Documents); documents > 1 { bunt.Fprintf(&buf, ", Aquamarine{*%s*}", text.Plural(documents, "document")) } return buf.String() } // HumanReadableLocation returns a human readable location with proper coloring func HumanReadableLocation(location string) string { if IsStdin(location) { return bunt.Sprint("_*stdin*_") } if _, err := os.Stat(location); err == nil { return bunt.Sprintf("*%s*", location) } if _, err := url.ParseRequestURI(location); err == nil { return bunt.Sprintf("CornflowerBlue{~%s~}", location) } return location } // LoadFiles concurrently loads two files from the provided locations func LoadFiles(locationA string, locationB string) (InputFile, InputFile, error) { type resultPair struct { result InputFile err error } fromChan := make(chan resultPair, 1) toChan := make(chan resultPair, 1) go func() { result, err := LoadFile(locationA) fromChan <- resultPair{result, err} }() go func() { result, err := LoadFile(locationB) toChan <- resultPair{result, err} }() from := <-fromChan if from.err != nil { return InputFile{}, InputFile{}, from.err } to := <-toChan if to.err != nil { return InputFile{}, InputFile{}, to.err } return from.result, to.result, nil } // 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) { if info, err := os.Stat(location); err == nil && info.IsDir() { return LoadDirectory(location) } var ( documents []*yamlv3.Node data []byte err error ) if data, err = getBytesFromLocation(location); err != nil { return InputFile{}, wrap.Errorf(err, "unable to load data from %s", HumanReadableLocation(location)) } if documents, err = LoadDocuments(data); err != nil { return InputFile{}, wrap.Errorf(err, "unable to parse data from %s", HumanReadableLocation(location)) } return InputFile{ Location: location, Documents: documents, }, nil } // LoadDirectory reads the provided location as a directory and processes all // files in the directory as documents func LoadDirectory(location string) (InputFile, error) { files, err := ioutil.ReadDir(location) if err != nil { return InputFile{}, wrap.Errorf(err, "failed to read files in directory %s", location) } sort.Slice(files, func(i, j int) bool { return strings.Compare(files[i].Name(), files[j].Name()) < 0 }) var result = InputFile{ Location: location, } for _, file := range files { bytes, err := getBytesFromLocation(filepath.Join(location, file.Name())) if err != nil { return InputFile{}, err } docs, err := LoadDocuments(bytes) if err != nil { return InputFile{}, err } result.Documents = append(result.Documents, docs...) result.Names = append(result.Names, file.Name()) } return result, nil } // 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) ([]*yamlv3.Node, 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) default: return LoadYAMLDocuments(input) } } // LoadJSONDocuments reads the provided input data slice as a JSON file with // potential multiple documents. Each document in the JSON stream results in an // entry of the result slice. func LoadJSONDocuments(input []byte) ([]*yamlv3.Node, error) { values := []*yamlv3.Node{} decoder := NewDecoderProxy(PreserveKeyOrderInJSON, bytes.NewReader(input)) for { var value interface{} err := decoder.Decode(&value) if err == io.EOF { break } if err != nil { return nil, err } node, err := asYAMLNode(value) if err != nil { return nil, err } values = append(values, node) } return values, nil } // LoadYAMLDocuments reads the provided input data slice as a YAML file with // potential multiple documents. Each document in the YAML stream results in an // entry of the result slice. func LoadYAMLDocuments(input []byte) ([]*yamlv3.Node, error) { documents := []*yamlv3.Node{} decoder := yamlv3.NewDecoder(bytes.NewReader(input)) for { var node yamlv3.Node err := decoder.Decode(&node) if err == io.EOF { break } if err != nil { return nil, err } documents = append(documents, &node) } return documents, nil } // LoadTOMLDocuments reads the provided input data slice as a TOML file, which // can only have one document. For the sake of having similar 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 Go-YAMLv3 Node // types to make it compatible with the rest of the package. func LoadTOMLDocuments(input []byte) ([]*yamlv3.Node, error) { var data interface{} if err := toml.Unmarshal(input, &data); err != nil { return nil, err } node, err := asYAMLNode(data) if err != nil { return nil, err } return []*yamlv3.Node{node}, nil } func getBytesFromLocation(location string) ([]byte, error) { // Handle special location "-" which refers to STDIN stream if IsStdin(location) { return ioutil.ReadAll(os.Stdin) } // Handle location as local file if there is a file at that location if _, err := os.Stat(location); err == nil { return ioutil.ReadFile(location) } // Handle location as a URI if it looks like one if _, err := url.ParseRequestURI(location); err == nil { response, err := http.Get(location) if err != nil { return nil, err } defer response.Body.Close() data, err := ioutil.ReadAll(response.Body) if response.StatusCode != http.StatusOK { return nil, fmt.Errorf("failed to retrieve data from location %s: %s", location, string(data)) } return data, err } // In any other case, bail out ... return nil, fmt.Errorf("unable to get any content using location %s: it is not a file or usable URI", location) } // IsStdin checks whether the provided input location refers to the dash // character which usually serves as the replacement to point to STDIN rather // than a file. func IsStdin(location string) bool { return strings.TrimSpace(location) == "-" } ytbx-1.4.4/input_test.go000066400000000000000000000071651417551137500152550ustar00rootroot00000000000000// Copyright © 2018 The Homeport Team // // 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. package ytbx_test import ( "fmt" "io/ioutil" "net/http" "net/http/httptest" "os" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/gorilla/mux" . "github.com/gonvenience/ytbx" ) var _ = Describe("Input test cases", func() { Context("Input data from local sources", func() { It("should load multiple JSON documents from one stream", func() { doc0, doc1 := `{ "key": "value" }`, `[ { "foo": "bar" } ]` documents, err := LoadDocuments([]byte(doc0 + "\n" + doc1)) Expect(err).ToNot(HaveOccurred()) Expect(len(documents)).To(BeEquivalentTo(2)) Expect(documents[0].Content[0]).To(BeAsNode(yml(doc0))) Expect(documents[1].Content[0]).To(BeAsNode(list(doc1))) }) }) Context("Input data from remote locations", func() { var server *httptest.Server BeforeEach(func() { r := NewRouter() r.HandleFunc("/v1/assets/{directory}/{filename}", func(w http.ResponseWriter, r *http.Request) { vars := Vars(r) directory := vars["directory"] filename := vars["filename"] location := assets(directory, filename) if _, err := os.Stat(location); os.IsNotExist(err) { w.WriteHeader(404) fmt.Fprintf(w, "File not found: %s/%s", directory, filename) return } data, err := ioutil.ReadFile(location) if err != nil { Fail(err.Error()) } w.WriteHeader(200) if _, err := w.Write(data); err != nil { Fail(err.Error()) } }) server = httptest.NewServer(r) }) AfterEach(func() { if server != nil { server.Close() } }) It("should load a YAML via a HTTP request", func() { inputfile, err := LoadFile(server.URL + "/v1/assets/examples/types.yml") Expect(err).To(BeNil()) Expect(inputfile).ToNot(BeNil()) }) It("should fail if the HTTP request fails", func() { fullUrl := server.URL + "/v1/assets/examples/does-not-exist.yml" _, err := LoadFile(fullUrl) Expect(err).ToNot(BeNil()) Expect(err.Error()).To(BeEquivalentTo("unable to load data from " + fullUrl + ": failed to retrieve data from location " + fullUrl + ": File not found: examples/does-not-exist.yml")) }) }) Context("Proper YAMLification of input sources", func() { It("should convert input TOML files to be YAMLish", func() { documents, err := LoadTOMLDocuments([]byte(exampleTOML)) Expect(err).ToNot(HaveOccurred()) Expect(len(documents)).To(BeEquivalentTo(1)) rootMap := documents[0].Content[0] Expect(rootMap.Content[0].Value).To(BeEquivalentTo("constraint")) Expect(rootMap.Content[2].Value).To(BeEquivalentTo("override")) }) }) }) ytbx-1.4.4/list_functions.go000066400000000000000000000066661417551137500161270ustar00rootroot00000000000000// Copyright © 2018 The Homeport Team // // 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. package ytbx import ( "fmt" yamlv3 "gopkg.in/yaml.v3" ) // GetIdentifierFromNamedList returns the identifier key used in the provided // list, or an empty string if there is none. // The identifier key is either 'name', 'key', or 'id'. func GetIdentifierFromNamedList(sequenceNode *yamlv3.Node) string { counters := map[string]int{} for _, mappingNode := range sequenceNode.Content { for i := 0; i < len(mappingNode.Content); i += 2 { k := mappingNode.Content[i] if _, ok := counters[k.Value]; !ok { counters[k.Value] = 0 } counters[k.Value]++ } } listLength := len(sequenceNode.Content) for _, identifier := range []string{"name", "key", "id"} { if count, ok := counters[identifier]; ok && count == listLength { return identifier } } return "" } // getEntryFromNamedList returns the entry that is identified by the identifier // key and a name, for example: `name: one` where name is the identifier key and // one the name. Function will return nil with bool false if there is no entry. func getEntryFromNamedList(sequenceNode *yamlv3.Node, identifier string, name string) (*yamlv3.Node, bool) { node, err := getEntryByIdentifierAndName(sequenceNode, identifier, name) return node, err == nil } func getEntryByIdentifierAndName(sequenceNode *yamlv3.Node, identifier string, name string) (*yamlv3.Node, error) { idx, err := getIndexByIdentifierAndName(sequenceNode, identifier, name) if err != nil { return nil, err } return sequenceNode.Content[idx], nil } func getIndexByIdentifierAndName(sequenceNode *yamlv3.Node, identifier string, name string) (int, error) { for idx, mappingNode := range sequenceNode.Content { for i := 0; i < len(mappingNode.Content); i += 2 { k, v := mappingNode.Content[i], mappingNode.Content[i+1] if k.Value == identifier && v.Value == name { return idx, nil } } } return -1, fmt.Errorf("there is no entry %s=%v in the list", identifier, name, ) } func listNamesOfNamedList(sequenceNode *yamlv3.Node, identifier string) ([]string, error) { result := make([]string, len(sequenceNode.Content)) for i, mappingNode := range sequenceNode.Content { if mappingNode.Kind != yamlv3.MappingNode { return nil, &NoNamedEntryListError{} } v, err := getValueByKey(mappingNode, identifier) if err != nil { return nil, err } result[i] = v.Value } return result, nil } ytbx-1.4.4/map_functions.go000066400000000000000000000041271417551137500157170ustar00rootroot00000000000000// Copyright © 2018 The Homeport Team // // 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. package ytbx import ( yamlv3 "gopkg.in/yaml.v3" ) // listKeys returns a list of the keys of a Go-YAML v3 MappingNode (map) func listKeys(mappingNode *yamlv3.Node) []string { keys := []string{} for i := 0; i < len(mappingNode.Content); i += 2 { keys = append(keys, mappingNode.Content[i].Value) } return keys } // ListStringKeys lists the keys in a MappingNode func ListStringKeys(mappingNode *yamlv3.Node) ([]string, error) { return listKeys(mappingNode), nil } // getValueByKey returns the value for a given key in a provided mapping node, // or nil with an error if there is no such entry. This is comparable to getting // a value from a map with `foobar[key]`. func getValueByKey(mappingNode *yamlv3.Node, key string) (*yamlv3.Node, error) { for i := 0; i < len(mappingNode.Content); i += 2 { k, v := mappingNode.Content[i], mappingNode.Content[i+1] if k.Value == key { return v, nil } } return nil, &KeyNotFoundInMapError{ MissingKey: key, AvailableKeys: listKeys(mappingNode), } } ytbx-1.4.4/path.go000066400000000000000000000356251417551137500140150ustar00rootroot00000000000000// Copyright © 2018 The Homeport Team // // 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. package ytbx import ( "fmt" "reflect" "regexp" "strconv" "strings" yamlv3 "gopkg.in/yaml.v3" ) var dotRegEx = regexp.MustCompile(`^((\d+):)?(.*)$`) // PathStyle is a custom type for supported path styles type PathStyle int // Supported styles are the Dot-Style (used by Spruce for example) and GoPatch // Style which is used by BOSH const ( DotStyle PathStyle = iota GoPatchStyle ) // Path points to a section in a data structure by using names to identify the // location. // Example: // --- // sizing: // api: // count: 2 // For example, `sizing.api.count` points to the key `sizing` of the root // element and in there to the key `api` and so on and so forth. type Path struct { Root *InputFile DocumentIdx int PathElements []PathElement } // PathElement represents one part of a path, which can either address an entry // in a map (by name), a named-entry list entry (key and name), or an entry in a // list (by index). type PathElement struct { Idx int Key string Name string } func (path Path) String() string { return path.ToGoPatchStyle() } // ToGoPatchStyle returns the path as a GoPatch style string. func (path *Path) ToGoPatchStyle() string { if len(path.PathElements) == 0 { return "/" } sections := []string{""} for _, element := range path.PathElements { switch { case element.Name != "" && element.Key == "": sections = append(sections, element.Name) case element.Name != "" && element.Key != "": sections = append(sections, fmt.Sprintf("%s=%s", element.Key, element.Name)) default: sections = append(sections, strconv.Itoa(element.Idx)) } } return strings.Join(sections, "/") } // ToDotStyle returns the path as a Dot-Style string. func (path *Path) ToDotStyle() string { sections := []string{} for _, element := range path.PathElements { switch { case element.Name != "": sections = append(sections, element.Name) case element.Idx >= 0: sections = append(sections, strconv.Itoa(element.Idx)) } } return strings.Join(sections, ".") } // RootDescription returns a description of the root level of this path, which // could be the number of the respective document inside a YAML or if available // the name of the document func (path *Path) RootDescription() string { if path.Root != nil && path.DocumentIdx < len(path.Root.Names) { return path.Root.Names[path.DocumentIdx] } // Note: human style counting that starts with 1 return fmt.Sprintf("document #%d", path.DocumentIdx+1) } // NewPathWithPathElement returns a new path based on a given path adding a new // path element. func NewPathWithPathElement(path Path, pathElement PathElement) Path { result := make([]PathElement, len(path.PathElements)) copy(result, path.PathElements) return Path{ Root: path.Root, DocumentIdx: path.DocumentIdx, PathElements: append(result, pathElement)} } // NewPathWithNamedElement returns a new path based on a given path adding a new // of type entry in map using the name. func NewPathWithNamedElement(path Path, name interface{}) Path { return NewPathWithPathElement(path, PathElement{ Idx: -1, Name: fmt.Sprintf("%v", name)}) } // NewPathWithNamedListElement returns a new path based on a given path adding a // new of type entry in a named-entry list by using key and name. func NewPathWithNamedListElement(path Path, identifier interface{}, name interface{}) Path { return NewPathWithPathElement(path, PathElement{ Idx: -1, Key: fmt.Sprintf("%v", identifier), Name: fmt.Sprintf("%v", name)}) } // NewPathWithIndexedListElement returns a new path based on a given path adding // a new of type list entry using the index. func NewPathWithIndexedListElement(path Path, idx int) Path { return NewPathWithPathElement(path, PathElement{ Idx: idx, }) } // ComparePathsByValue returns all Path structure that have the same path value func ComparePathsByValue(fromLocation string, toLocation string, duplicatePaths []Path) ([]Path, error) { from, err := LoadFile(fromLocation) if err != nil { return nil, err } to, err := LoadFile(toLocation) if err != nil { return nil, err } if len(from.Documents) > 1 || len(to.Documents) > 1 { return nil, fmt.Errorf("input files have more than one document, which is not supported yet") } duplicatePathsWithTheSameValue := []Path{} for _, path := range duplicatePaths { fromValue, err := Grab(from.Documents[0], path.ToGoPatchStyle()) if err != nil { return nil, err } toValue, err := Grab(to.Documents[0], path.ToGoPatchStyle()) if err != nil { return nil, err } if reflect.DeepEqual(fromValue, toValue) { duplicatePathsWithTheSameValue = append(duplicatePathsWithTheSameValue, path) } } return duplicatePathsWithTheSameValue, nil } // ComparePaths returns all duplicate Path structures between two documents. func ComparePaths(fromLocation string, toLocation string, compareByValue bool) ([]Path, error) { var duplicatePaths []Path pathsFromLocation, err := ListPaths(fromLocation) if err != nil { return nil, err } pathsToLocation, err := ListPaths(toLocation) if err != nil { return nil, err } lookup := map[string]struct{}{} for _, pathsFrom := range pathsFromLocation { lookup[pathsFrom.ToGoPatchStyle()] = struct{}{} } for _, pathsTo := range pathsToLocation { if _, ok := lookup[pathsTo.ToGoPatchStyle()]; ok { duplicatePaths = append(duplicatePaths, pathsTo) } } if !compareByValue { return duplicatePaths, nil } return ComparePathsByValue(fromLocation, toLocation, duplicatePaths) } // ListPaths returns all paths in the documents using the provided choice of // path style. func ListPaths(location string) ([]Path, error) { inputfile, err := LoadFile(location) if err != nil { return nil, err } paths := []Path{} for idx, document := range inputfile.Documents { root := Path{DocumentIdx: idx} traverseTree(root, nil, document, func(path Path, _ *yamlv3.Node, _ *yamlv3.Node) { paths = append(paths, path) }) } return paths, nil } // IsPathInTree returns whether the provided path is in the given YAML structure func IsPathInTree(tree *yamlv3.Node, pathString string) (bool, error) { searchPath, err := ParsePathString(pathString, tree) if err != nil { return false, err } resultChan := make(chan bool) go func() { for _, node := range tree.Content { traverseTree(Path{}, nil, node, func(path Path, _ *yamlv3.Node, _ *yamlv3.Node) { if path.ToGoPatchStyle() == searchPath.ToGoPatchStyle() { resultChan <- true } }) resultChan <- false } }() return <-resultChan, nil } func traverseTree(path Path, parent *yamlv3.Node, node *yamlv3.Node, leafFunc func(path Path, parent *yamlv3.Node, leaf *yamlv3.Node)) { switch node.Kind { case yamlv3.DocumentNode: traverseTree( path, node, node.Content[0], leafFunc, ) case yamlv3.SequenceNode: if identifier := GetIdentifierFromNamedList(node); identifier != "" { for _, mappingNode := range node.Content { name, _ := getValueByKey(mappingNode, identifier) tmpPath := NewPathWithNamedListElement(path, identifier, name.Value) for i := 0; i < len(mappingNode.Content); i += 2 { k, v := mappingNode.Content[i], mappingNode.Content[i+1] if k.Value == identifier { // skip the identifier mapping entry continue } traverseTree( NewPathWithNamedElement(tmpPath, k.Value), node, v, leafFunc, ) } } } else { for idx, entry := range node.Content { traverseTree( NewPathWithIndexedListElement(path, idx), node, entry, leafFunc, ) } } case yamlv3.MappingNode: for i := 0; i < len(node.Content); i += 2 { k, v := node.Content[i], node.Content[i+1] traverseTree( NewPathWithNamedElement(path, k.Value), node, v, leafFunc, ) } default: leafFunc(path, parent, node) } } // ParseGoPatchStylePathString returns a path by parsing a string representation // which is assumed to be a GoPatch style path. func ParseGoPatchStylePathString(path string) (Path, error) { // Special case for root path if path == "/" { return Path{DocumentIdx: 0, PathElements: nil}, nil } // Hacky solution to deal with escaped slashes, replace them with a "safe" // replacement string that is later resolved into a simple slash path = strings.Replace(path, `\/`, `%2F`, -1) elements := make([]PathElement, 0) for i, section := range strings.Split(path, "/") { if i == 0 { continue } keyNameSplit := strings.Split(section, "=") switch len(keyNameSplit) { case 1: if idx, err := strconv.Atoi(keyNameSplit[0]); err == nil { elements = append(elements, PathElement{ Idx: idx, }) } else { elements = append(elements, PathElement{ Idx: -1, Name: strings.Replace(keyNameSplit[0], `%2F`, "/", -1), }) } case 2: elements = append(elements, PathElement{Idx: -1, Key: strings.Replace(keyNameSplit[0], `%2F`, "/", -1), Name: strings.Replace(keyNameSplit[1], `%2F`, "/", -1), }) default: return Path{}, &InvalidPathString{ Style: GoPatchStyle, PathString: path, Explanation: fmt.Sprintf("element '%s' cannot contain more than one equal sign", section), } } } return Path{DocumentIdx: 0, PathElements: elements}, nil } // ParseDotStylePathString returns a path by parsing a string representation // which is assumed to be a Dot-Style path. func ParseDotStylePathString(path string, node *yamlv3.Node) (Path, error) { if node.Kind != yamlv3.DocumentNode { return Path{}, fmt.Errorf("node has to be of kind DocumentNode for parsing a document path") } elements := make([]PathElement, 0) pointer := node.Content[0] for _, section := range strings.Split(path, ".") { switch { case pointer == nil: // If the pointer is nil, it means that the previous section of the path // string could not be found in the data structure and that all remaining // sections are assumed to be of type map. elements = append(elements, PathElement{Idx: -1, Name: section}) case pointer.Kind == yamlv3.MappingNode: if value, err := getValueByKey(pointer, section); err == nil { pointer = value elements = append(elements, PathElement{Idx: -1, Name: section}) } else { pointer = nil elements = append(elements, PathElement{Idx: -1, Name: section}) } case pointer.Kind == yamlv3.SequenceNode: list := pointer.Content if id, err := strconv.Atoi(section); err == nil { if id < 0 || id >= len(list) { return Path{}, &InvalidPathString{ Style: DotStyle, PathString: path, Explanation: fmt.Sprintf("provided list index %d is not in range: 0..%d", id, len(list)-1), } } pointer = list[id] elements = append(elements, PathElement{Idx: id}) } else { identifier := GetIdentifierFromNamedList(pointer) value, ok := getEntryFromNamedList(pointer, identifier, section) if !ok { names, err := listNamesOfNamedList(pointer, identifier) if err != nil { return Path{}, &InvalidPathString{ Style: DotStyle, PathString: path, Explanation: fmt.Sprintf("provided named list entry '%s' cannot be found in list", section), } } return Path{}, &InvalidPathString{ Style: DotStyle, PathString: path, Explanation: fmt.Sprintf("provided named list entry '%s' cannot be found in list, available names are: %s", section, strings.Join(names, ", ")), } } pointer = value elements = append(elements, PathElement{Idx: -1, Key: identifier, Name: section}) } } } return Path{DocumentIdx: 0, PathElements: elements}, nil } // ParseDotStylePathStringUnsafe returns a path by parsing a string // representation, which is assumed to be a Dot-Style path, but *without* // checking it against a YAML Node func ParseDotStylePathStringUnsafe(path string) (Path, error) { matches := dotRegEx.FindStringSubmatch(path) if matches == nil { return Path{}, NewInvalidPathError(GoPatchStyle, path, "failed to parse path string, because path does not match expected format", ) } var documentIdx int if len(matches[2]) > 0 { var err error documentIdx, err = strconv.Atoi(matches[2]) if err != nil { return Path{}, NewInvalidPathError(GoPatchStyle, path, "failed to parse path string, cannot parse document index: %s", matches[2], ) } } // Reset path variable to only contain the raw path string path = matches[3] var elements []PathElement for _, section := range strings.Split(path, ".") { if idx, err := strconv.Atoi(section); err == nil { elements = append(elements, PathElement{Idx: idx}) } else { // This is the unsafe part here, since there is no YAML node to // check against, it can only be assumed it is a mapping elements = append(elements, PathElement{Idx: -1, Name: section}) } } return Path{DocumentIdx: documentIdx, PathElements: elements}, nil } // ParsePathString returns a path by parsing a string representation // of a path, which can be one of the supported types. func ParsePathString(pathString string, node *yamlv3.Node) (Path, error) { if strings.HasPrefix(pathString, "/") { return ParseGoPatchStylePathString(pathString) } return ParseDotStylePathString(pathString, node) } // ParsePathStringUnsafe returns a path by parsing a string representation of a // path, which can either be GoPatch or DotStyle, but will not check the path // elements against a given YAML document to verify the types (unsafe) func ParsePathStringUnsafe(pathString string) (Path, error) { if strings.HasPrefix(pathString, "/") { return ParseGoPatchStylePathString(pathString) } return ParseDotStylePathStringUnsafe(pathString) } func (element PathElement) isMapElement() bool { return len(element.Key) == 0 && len(element.Name) > 0 } func (element PathElement) isComplexListElement() bool { return len(element.Key) > 0 && len(element.Name) > 0 } func (element PathElement) isSimpleListElement() bool { return len(element.Key) == 0 && len(element.Name) == 0 } ytbx-1.4.4/path_test.go000066400000000000000000000233101417551137500150400ustar00rootroot00000000000000// Copyright © 2018 The Homeport Team // // 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. package ytbx_test import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/gonvenience/ytbx" yamlv3 "gopkg.in/yaml.v3" ) func getExampleDocument() *yamlv3.Node { input, err := LoadFile(assets("testbed", "example.yml")) Expect(err).ToNot(HaveOccurred()) Expect(len(input.Documents)).To(BeIdenticalTo(1)) return input.Documents[0] } var _ = Describe("path tests", func() { Context("parse dot-style path strings into a path", func() { It("should parse string with only map elements", func() { path, err := ParseDotStylePathString("yaml.structure.somekey", getExampleDocument()) Expect(err).To(BeNil()) Expect(path).To(BeEquivalentTo(Path{DocumentIdx: 0, PathElements: []PathElement{ {Idx: -1, Key: "", Name: "yaml"}, {Idx: -1, Key: "", Name: "structure"}, {Idx: -1, Key: "", Name: "somekey"}, }})) }) It("should parse string with map and named-entry list elements", func() { path, err := ParseDotStylePathString("list.one.somekey", getExampleDocument()) Expect(err).To(BeNil()) Expect(path).To(BeEquivalentTo(Path{DocumentIdx: 0, PathElements: []PathElement{ {Idx: -1, Key: "", Name: "list"}, {Idx: -1, Key: "name", Name: "one"}, {Idx: -1, Key: "", Name: "somekey"}, }})) }) It("should parse string with simple list entry", func() { path, err := ParseDotStylePathString("simpleList.1", getExampleDocument()) Expect(err).To(BeNil()) Expect(path).To(BeEquivalentTo(Path{DocumentIdx: 0, PathElements: []PathElement{ {Idx: -1, Key: "", Name: "simpleList"}, {Idx: 1}, }})) }) It("should parse string with non-existing map elements", func() { path, err := ParseDotStylePathString("yaml.update.newkey", getExampleDocument()) Expect(err).ToNot(HaveOccurred()) Expect(path).To(BeEquivalentTo(Path{DocumentIdx: 0, PathElements: []PathElement{ {Idx: -1, Key: "", Name: "yaml"}, {Idx: -1, Key: "", Name: "update"}, {Idx: -1, Key: "", Name: "newkey"}, }})) }) It("should parse string with non-existing map and named-entry list elements", func() { path, err := ParseDotStylePathString("list.one.newkey", getExampleDocument()) Expect(err).To(BeNil()) Expect(path).To(BeEquivalentTo(Path{DocumentIdx: 0, PathElements: []PathElement{ {Idx: -1, Key: "", Name: "list"}, {Idx: -1, Key: "name", Name: "one"}, {Idx: -1, Key: "", Name: "newkey"}, }})) }) It("should parse unsafe paths too", func() { Expect(ParseDotStylePathStringUnsafe("list.one.newkey")).To(BeEquivalentTo(Path{DocumentIdx: 0, PathElements: []PathElement{ {Idx: -1, Key: "", Name: "list"}, {Idx: -1, Key: "", Name: "one"}, {Idx: -1, Key: "", Name: "newkey"}, }})) Expect(ParseDotStylePathStringUnsafe("list.0.newkey")).To(BeEquivalentTo(Path{DocumentIdx: 0, PathElements: []PathElement{ {Idx: -1, Key: "", Name: "list"}, {Idx: 0, Key: "", Name: ""}, {Idx: -1, Key: "", Name: "newkey"}, }})) }) It("should parse an unspecified path type without checking against a YAML document (unsafe)", func() { Expect(ParsePathStringUnsafe("list.one.newkey")).To(BeEquivalentTo(Path{DocumentIdx: 0, PathElements: []PathElement{ {Idx: -1, Key: "", Name: "list"}, {Idx: -1, Key: "", Name: "one"}, {Idx: -1, Key: "", Name: "newkey"}, }})) Expect(ParsePathStringUnsafe("list.0.newkey")).To(BeEquivalentTo(Path{DocumentIdx: 0, PathElements: []PathElement{ {Idx: -1, Key: "", Name: "list"}, {Idx: 0, Key: "", Name: ""}, {Idx: -1, Key: "", Name: "newkey"}, }})) Expect(ParsePathStringUnsafe("/list/one/newkey")).To(BeEquivalentTo(Path{DocumentIdx: 0, PathElements: []PathElement{ {Idx: -1, Key: "", Name: "list"}, {Idx: -1, Key: "", Name: "one"}, {Idx: -1, Key: "", Name: "newkey"}, }})) Expect(ParsePathStringUnsafe("/list/0/newkey")).To(BeEquivalentTo(Path{DocumentIdx: 0, PathElements: []PathElement{ {Idx: -1, Key: "", Name: "list"}, {Idx: 0, Key: "", Name: ""}, {Idx: -1, Key: "", Name: "newkey"}, }})) }) }) Context("parse go-patch style path strings into paths", func() { It("should parse an input string using go-patch style into a path (only maps)", func() { path, err := ParseGoPatchStylePathString("/yaml/structure/somekey") Expect(err).To(BeNil()) Expect(path).To(BeEquivalentTo(Path{DocumentIdx: 0, PathElements: []PathElement{ {Idx: -1, Key: "", Name: "yaml"}, {Idx: -1, Key: "", Name: "structure"}, {Idx: -1, Key: "", Name: "somekey"}, }})) }) It("should parse an input string using go-patch style into a path (maps and named-entry lists)", func() { path, err := ParseGoPatchStylePathString("/list/name=one/somekey") Expect(err).To(BeNil()) Expect(path).To(BeEquivalentTo(Path{DocumentIdx: 0, PathElements: []PathElement{ {Idx: -1, Key: "", Name: "list"}, {Idx: -1, Key: "name", Name: "one"}, {Idx: -1, Key: "", Name: "somekey"}, }})) }) It("should parse an input string using go-patch style into a path (simple list)", func() { path, err := ParseGoPatchStylePathString("/simpleList/1") Expect(err).To(BeNil()) Expect(path).To(BeEquivalentTo(Path{DocumentIdx: 0, PathElements: []PathElement{ {Idx: -1, Key: "", Name: "simpleList"}, {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})) }) It("should parse real-life scenario paths with mixed types", func() { path, err := ParseGoPatchStylePathString("/resource_pools/name=concourse_resource_pool/cloud_properties/datacenters/0/clusters") Expect(err).ToNot(HaveOccurred()) Expect(path).To(BeEquivalentTo(Path{DocumentIdx: 0, PathElements: []PathElement{ {Idx: -1, Key: "", Name: "resource_pools"}, {Idx: -1, Key: "name", Name: "concourse_resource_pool"}, {Idx: -1, Key: "", Name: "cloud_properties"}, {Idx: -1, Key: "", Name: "datacenters"}, {Idx: 0}, {Idx: -1, Key: "", Name: "clusters"}, }})) }) It("should parse path strings with escaped slashes", func() { path, err := ParseGoPatchStylePathString("/foo/name=bar.com\\/id/string") Expect(err).ToNot(HaveOccurred()) Expect(path).To(BeEquivalentTo(Path{DocumentIdx: 0, PathElements: []PathElement{ {Idx: -1, Key: "", Name: "foo"}, {Idx: -1, Key: "name", Name: "bar.com/id"}, {Idx: -1, Key: "", Name: "string"}, }})) }) }) Context("compare paths between two files", func() { It("should find only duplicate paths", func() { list, err := ComparePaths(assets("testbed", "sample_a.yml"), assets("testbed", "sample_b.yml"), false) Expect(err).ToNot(HaveOccurred()) listOfPaths := []Path{ { DocumentIdx: 0, PathElements: []PathElement{ {Idx: -1, Key: "", Name: "yaml"}, {Idx: -1, Key: "", Name: "structure"}, {Idx: -1, Key: "", Name: "somekey"}, }, }, { DocumentIdx: 0, PathElements: []PathElement{ {Idx: -1, Key: "", Name: "yaml"}, {Idx: -1, Key: "", Name: "structure"}, {Idx: -1, Key: "", Name: "dot"}, }, }, { DocumentIdx: 0, PathElements: []PathElement{ {Idx: -1, Key: "", Name: "list"}, {Idx: -1, Key: "name", Name: "sametwo"}, {Idx: -1, Key: "", Name: "somekey"}, }, }, } Expect(list).To(BeEquivalentTo(listOfPaths)) }) It("should find only paths with the same value", func() { list, err := ComparePaths(assets("testbed", "sample_a.yml"), assets("testbed", "sample_b.yml"), true) Expect(err).ToNot(HaveOccurred()) listOfPathsWithSameValue := []Path{ { DocumentIdx: 0, PathElements: []PathElement{ {Idx: -1, Key: "", Name: "yaml"}, {Idx: -1, Key: "", Name: "structure"}, {Idx: -1, Key: "", Name: "dot"}, }, }, { DocumentIdx: 0, PathElements: []PathElement{ {Idx: -1, Key: "", Name: "list"}, {Idx: -1, Key: "name", Name: "sametwo"}, {Idx: -1, Key: "", Name: "somekey"}, }, }, } Expect(list).To(BeEquivalentTo(listOfPathsWithSameValue)) }) }) Context("checking for path in YAML", func() { It("should check whether the provided path is in the YAML", func() { example := yml(assets("examples", "types.yml")) Expect(IsPathInTree(example, "/yaml/map/before")).To(BeTrue()) Expect(IsPathInTree(example, "/yaml/map/nope")).To(BeFalse()) Expect(IsPathInTree(example, "/yaml/simple-list/0")).To(BeTrue()) Expect(IsPathInTree(example, "/yaml/simple-list/5")).To(BeFalse()) Expect(IsPathInTree(example, "/yaml/named-entry-list-using-name/name=A/foo")).To(BeTrue()) Expect(IsPathInTree(example, "/yaml/named-entry-list-using-name/name=nope/foo")).To(BeFalse()) }) }) }) ytbx-1.4.4/restructure.go000066400000000000000000000132551417551137500154430ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // 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. package ytbx import ( "sort" yamlv3 "gopkg.in/yaml.v3" ) // DisableRemainingKeySort disables that during restructuring of map keys, all // unknown keys are also sorted in such a way that it improves the readability. var DisableRemainingKeySort = false var knownKeyOrders = [][]string{ {"name", "director_uuid", "releases", "instance_groups", "networks", "resource_pools", "compilation"}, {"name", "url", "version", "sha1"}, // Concourse (https://concourse-ci.org/pipelines.html, https://concourse-ci.org/steps.html, https://concourse-ci.org/resources.html) {"jobs", "resources", "resource_types"}, {"name", "type", "source"}, {"get"}, {"put"}, {"task"}, // SUSE SCF role manifest (https://github.com/SUSE/scf/blob/develop/container-host-files/etc/scf/config/role-manifest.yml) {"releases", "instance_groups", "configuration", "variables"}, {"auth", "templates"}, // Universal default #1 ... name should always be first {"name"}, // Universal default #2 ... key should always be first {"key"}, // Universal default #3 ... id should always be first {"id"}, } func lookupMap(list []string) map[string]int { result := make(map[string]int, len(list)) for idx, entry := range list { result[entry] = idx } return result } func lookupMapOfContentList(list []*yamlv3.Node) map[string]int { lookup := make(map[string]int, len(list)) for i := 0; i < len(list); i += 2 { lookup[list[i].Value] = i } return lookup } func maxDepth(node *yamlv3.Node) (max int) { rootPath, _ := ParseGoPatchStylePathString("/") traverseTree( rootPath, nil, node, func(p Path, _ *yamlv3.Node, _ *yamlv3.Node) { if depth := len(p.PathElements); depth > max { max = depth } }, ) return max } func countCommonKeys(keys []string, list []string) (counter int) { lookup := lookupMap(keys) for _, key := range list { if _, ok := lookup[key]; ok { counter++ } } return } func commonKeys(setA []string, setB []string) []string { result, lookup := []string{}, lookupMap(setB) for _, entry := range setA { if _, ok := lookup[entry]; ok { result = append(result, entry) } } return result } func reorderKeyValuePairsInMappingNodeContent(mappingNode *yamlv3.Node, keys []string) { // Create list with all keys, that are not part of the provided list of keys remainingKeys, keysLookup := []string{}, lookupMap(keys) for i := 0; i < len(mappingNode.Content); i += 2 { key := mappingNode.Content[i].Value if _, ok := keysLookup[key]; !ok { remainingKeys = append(remainingKeys, key) } } // Sort remaining keys by sorting long and possibly hard to read structure // to the end of the mapping if !DisableRemainingKeySort { sort.Slice(remainingKeys, func(i, j int) bool { valI, _ := getValueByKey(mappingNode, remainingKeys[i]) valJ, _ := getValueByKey(mappingNode, remainingKeys[j]) return maxDepth(valI) < maxDepth(valJ) }) } // Rebuild a new YAML Node list (content) key by key by using first the keys // from the reorder list and then all remaining keys content, contentLookup := []*yamlv3.Node{}, lookupMapOfContentList(mappingNode.Content) for _, key := range append(keys, remainingKeys...) { idx := contentLookup[key] content = append(content, mappingNode.Content[idx], mappingNode.Content[idx+1], ) } mappingNode.Content = content } func getSuitableReorderFunction(keys []string) func(*yamlv3.Node) { topCandidateIdx, topCandidateHits := -1, -1 for idx, candidate := range knownKeyOrders { if count := countCommonKeys(keys, candidate); count > 0 && count > topCandidateHits { topCandidateIdx = idx topCandidateHits = count } } if topCandidateIdx >= 0 { return func(input *yamlv3.Node) { reorderKeyValuePairsInMappingNodeContent( input, commonKeys(knownKeyOrders[topCandidateIdx], keys), ) } } return nil } // RestructureObject takes an object and traverses down any sub elements such as // list entries or map values to recursively call restructure itself. On YAML // MappingNodes, it will use a look-up mechanism to decide if the order of key // in that map need to be rearranged to meet some known established human order. func RestructureObject(node *yamlv3.Node) { switch node.Kind { case yamlv3.DocumentNode: RestructureObject(node.Content[0]) case yamlv3.MappingNode: keys := listKeys(node) if fn := getSuitableReorderFunction(keys); fn != nil { fn(node) } // Restructure the values of the respective keys of this YAML MapSlice for i := 0; i < len(node.Content); i += 2 { RestructureObject(node.Content[i+1]) } case yamlv3.SequenceNode: for i := range node.Content { RestructureObject(node.Content[i]) } } } ytbx-1.4.4/restructure_test.go000066400000000000000000000060471417551137500165030ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // 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. package ytbx_test import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/gonvenience/ytbx" ) var _ = Describe("Restructure order of map keys", func() { Context("YAML MapSlice key reordering of the MapSlice itself", func() { It("should restructure Concourse root level keys", func() { example := yml("{ groups: [], jobs: [], resources: [], resource_types: [] }") RestructureObject(example) keys, err := ListStringKeys(example) Expect(err).ToNot(HaveOccurred()) Expect(keys).To(BeEquivalentTo([]string{"jobs", "resources", "resource_types", "groups"})) }) It("should restructure Concourse resource and resource_type keys", func() { example := yml("{ source: {}, name: {}, type: {}, privileged: {} }") RestructureObject(example) keys, err := ListStringKeys(example) Expect(err).ToNot(HaveOccurred()) Expect(keys).To(BeEquivalentTo([]string{"name", "type", "source", "privileged"})) }) }) Context("YAML MapSlice key reordering of the MapSlice values", func() { It("should restructure Concourse resource keys as part as part of a MapSlice value", func() { example := yml("{ resources: [ { privileged: false, source: { branch: foo, paths: [] }, name: myname, type: mytype } ] }") RestructureObject(example) keys, err := ListStringKeys(example.Content[1].Content[0]) Expect(err).ToNot(HaveOccurred()) Expect(keys).To(BeEquivalentTo([]string{"name", "type", "source", "privileged"})) }) }) Context("Restructure code tries to rearrange even unknown keys", func() { It("should reorder map keys in a somehow more readable way", func() { example := yml(`{"list":["one","two","three"], "some":{"deep":{"structure":{"where":{"you":{"loose":{"focus":{"one":1,"two":2}}}}}}}, "name":"here", "release":"this"}`) RestructureObject(example) keys, err := ListStringKeys(example) Expect(err).ToNot(HaveOccurred()) Expect(keys).To(BeEquivalentTo([]string{"name", "release", "list", "some"})) }) }) }) ytbx-1.4.4/ytbx_suite_test.go000066400000000000000000000123401417551137500163040ustar00rootroot00000000000000// Copyright © 2018 The Homeport Team // // 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. package ytbx_test import ( "fmt" "os" "path/filepath" "strconv" "testing" . "github.com/gonvenience/bunt" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/gonvenience/neat" "github.com/onsi/gomega/types" yamlv3 "gopkg.in/yaml.v3" "github.com/gonvenience/ytbx" ) var exampleTOML = ` required = ["gopkg.in/fsnotify.v1"] [prune] go-tests = true unused-packages = true non-go = true [[constraint]] name = "gopkg.in/fsnotify.v1" source = "https://github.com/fsnotify/fsnotify.git" [[constraint]] name = "k8s.io/helm" branch = "release-2.10" [[override]] name = "gopkg.in/yaml.v2" revision = "670d4cfef0544295bc27a114dbac37980d83185a" [[override]] branch = "release-1.10" name = "k8s.io/api" [[override]] branch = "release-1.10" name = "k8s.io/apimachinery" [[override]] branch = "release-7.0" name = "k8s.io/client-go" ` func TestYtbx(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "ytbx suite") } var _ = BeforeSuite(func() { SetColorSettings(OFF, OFF) }) var _ = AfterSuite(func() { SetColorSettings(AUTO, AUTO) }) func assets(pathElement ...string) string { targetPath := filepath.Join(append( []string{"assets"}, pathElement..., )...) abs, err := filepath.Abs(targetPath) Expect(err).ToNot(HaveOccurred()) return abs } func yml(input string) *yamlv3.Node { // If input is a file location, load this as YAML if _, err := os.Open(input); err == nil { var content ytbx.InputFile var err error if content, err = ytbx.LoadFile(input); err != nil { Fail(fmt.Sprintf("Failed to load YAML MapSlice from '%s': %s", input, err.Error())) } if len(content.Documents) > 1 { Fail(fmt.Sprintf("Failed to load YAML MapSlice from '%s': Provided file contains more than one document", input)) } return content.Documents[0] } // Load YAML by parsing the actual string as YAML if it was not a file location document := singleDoc(input) return document.Content[0] } func list(input string) *yamlv3.Node { document := singleDoc(input) return document.Content[0] } func singleDoc(input string) *yamlv3.Node { docs, err := ytbx.LoadYAMLDocuments([]byte(input)) if err != nil { Fail(fmt.Sprintf("Failed to parse as YAML:\n%s\n\n%v", input, err)) } if len(docs) > 1 { Fail(fmt.Sprintf("Failed to use YAML, because it contains multiple documents:\n%s\n", input)) } return docs[0] } func grab(node *yamlv3.Node, path string) interface{} { v, err := ytbx.Grab(node, path) if err != nil { out, _ := neat.ToYAMLString(node) Fail(fmt.Sprintf("Failed to grab by path %s from %s", path, out)) } switch v.Tag { case "!!str": return v.Value case "!!int": i, _ := strconv.Atoi(v.Value) return i } return v } func grabError(node *yamlv3.Node, path string) string { value, err := ytbx.Grab(node, path) Expect(value).To(BeNil()) Expect(err).ToNot(BeNil()) return err.Error() } func BeAsNode(expected *yamlv3.Node) types.GomegaMatcher { return &nodeMatcher{ expected: expected, } } type nodeMatcher struct { expected *yamlv3.Node } func (matcher *nodeMatcher) Match(actual interface{}) (success bool, err error) { actualNodePtr, ok := actual.(*yamlv3.Node) if !ok { return false, fmt.Errorf("BeAsNode matcher expected a Go YAML v3 Node, not %T", actual) } return isSameNode(actualNodePtr, matcher.expected) } func (matcher *nodeMatcher) FailureMessage(actual interface{}) string { return fmt.Sprintf("Expected\n\t%#v\n"+"to be same as\n\t%#v", actual, matcher.expected) } func (matcher *nodeMatcher) NegatedFailureMessage(actual interface{}) string { return fmt.Sprintf("Expected\n\t%#v\nnot to be same as\n\t%#v", actual, matcher.expected, ) } func isSameNode(a, b *yamlv3.Node) (bool, error) { if a == nil && b == nil { return true, nil } if (a == nil && b != nil) || (a != nil && b == nil) { return false, nil } if a.Kind != b.Kind { return false, nil } if a.Tag != b.Tag { return false, nil } if a.Value != b.Value { return false, nil } if len(a.Content) != len(b.Content) { return false, nil } for i := range a.Content { if same, err := isSameNode(a.Content[i], b.Content[i]); !same { return same, err } } return true, nil }