diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index e921bc0f71e..efa1b8f58be 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -986,6 +986,10 @@ "ImportPath": "github.com/exponent-io/jsonpath", "Rev": "d6023ce2651d8eafb5c75bb0c7167536102ec9f5" }, + { + "ImportPath": "github.com/fatih/camelcase", + "Rev": "f6a740d52f961c60348ebb109adde9f4635d7540" + }, { "ImportPath": "github.com/fsnotify/fsnotify", "Comment": "v1.3.1-1-gf12c623", diff --git a/Godeps/LICENSES b/Godeps/LICENSES index 11f4f502d39..2f9e69d23fc 100644 --- a/Godeps/LICENSES +++ b/Godeps/LICENSES @@ -34163,6 +34163,34 @@ SOFTWARE. ================================================================================ +================================================================================ += vendor/github.com/fatih/camelcase licensed under: = + +The MIT License (MIT) + +Copyright (c) 2015 Fatih Arslan + +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. + += vendor/github.com/fatih/camelcase/LICENSE.md 4c59b216ce25dc182cdb837e07ba07c0 - +================================================================================ + + ================================================================================ = vendor/github.com/fsnotify/fsnotify licensed under: = diff --git a/pkg/printers/BUILD b/pkg/printers/BUILD index 3c60890ef9b..8e6e4b4da2b 100644 --- a/pkg/printers/BUILD +++ b/pkg/printers/BUILD @@ -25,6 +25,8 @@ go_library( ], tags = ["automanaged"], deps = [ + "//pkg/util/slice:go_default_library", + "//vendor/github.com/fatih/camelcase:go_default_library", "//vendor/github.com/ghodss/yaml:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", @@ -64,3 +66,11 @@ filegroup( ], tags = ["automanaged"], ) + +go_test( + name = "go_default_test", + srcs = ["humanreadable_test.go"], + library = ":go_default_library", + tags = ["automanaged"], + deps = ["//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library"], +) diff --git a/pkg/printers/humanreadable.go b/pkg/printers/humanreadable.go index 69aa34fa051..cce89c69487 100644 --- a/pkg/printers/humanreadable.go +++ b/pkg/printers/humanreadable.go @@ -21,15 +21,18 @@ import ( "fmt" "io" "reflect" + "sort" "strings" "text/tabwriter" + "github.com/fatih/camelcase" "github.com/golang/glog" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kubernetes/pkg/util/slice" ) var withNamespacePrefixColumns = []string{"NAMESPACE"} // TODO(erictune): print cluster name too. @@ -201,8 +204,51 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er } if _, err := meta.Accessor(obj); err == nil { + // we don't recognize this type, but we can still attempt to print some reasonable information about. + unstructured, ok := obj.(runtime.Unstructured) + if !ok { + return fmt.Errorf("error: unknown type %#v", obj) + } + + content := unstructured.UnstructuredContent() + + // we'll elect a few more fields to print depending on how much columns are already taken + maxDiscoveredFieldsToPrint := 3 + maxDiscoveredFieldsToPrint = maxDiscoveredFieldsToPrint - len(h.options.ColumnLabels) + if h.options.WithNamespace { // where's my ternary + maxDiscoveredFieldsToPrint-- + } + if h.options.ShowLabels { + maxDiscoveredFieldsToPrint-- + } + if maxDiscoveredFieldsToPrint < 0 { + maxDiscoveredFieldsToPrint = 0 + } + + var discoveredFieldNames []string // we want it predictable so this will be used to sort + ignoreIfDiscovered := []string{"kind", "apiVersion"} // these are already covered + for field, value := range content { + if slice.ContainsString(ignoreIfDiscovered, field, nil) { + continue + } + switch value.(type) { + case map[string]interface{}: + // just simpler types + continue + } + discoveredFieldNames = append(discoveredFieldNames, field) + } + sort.Strings(discoveredFieldNames) + if len(discoveredFieldNames) > maxDiscoveredFieldsToPrint { + discoveredFieldNames = discoveredFieldNames[:maxDiscoveredFieldsToPrint] + } + if !h.options.NoHeaders && t != h.lastType { headers := []string{"NAME", "KIND"} + for _, discoveredField := range discoveredFieldNames { + fieldAsHeader := strings.ToUpper(strings.Join(camelcase.Split(discoveredField), " ")) + headers = append(headers, fieldAsHeader) + } headers = append(headers, formatLabelHeaders(h.options.ColumnLabels)...) // LABELS is always the last column. headers = append(headers, formatShowLabelsHeader(h.options.ShowLabels, t)...) @@ -213,13 +259,8 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er h.lastType = t } - // we don't recognize this type, but we can still attempt to print some reasonable information about. - unstructured, ok := obj.(runtime.Unstructured) - if !ok { - return fmt.Errorf("error: unknown type %#v", obj) - } // if the error isn't nil, report the "I don't recognize this" error - if err := printUnstructured(unstructured, w, h.options); err != nil { + if err := printUnstructured(unstructured, w, discoveredFieldNames, h.options); err != nil { return err } return nil @@ -230,7 +271,7 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er } // TODO: this method assumes the meta/v1 server API, so should be refactored out of this package -func printUnstructured(unstructured runtime.Unstructured, w io.Writer, options PrintOptions) error { +func printUnstructured(unstructured runtime.Unstructured, w io.Writer, additionalFields []string, options PrintOptions) error { metadata, err := meta.Accessor(unstructured) if err != nil { return err @@ -258,11 +299,26 @@ func printUnstructured(unstructured runtime.Unstructured, w io.Writer, options P kind = kind + "." + version.Version + "." + version.Group } } + name := formatResourceName(options.Kind, metadata.GetName(), options.WithKind) if _, err := fmt.Fprintf(w, "%s\t%s", name, kind); err != nil { return err } + for _, field := range additionalFields { + if value, ok := content[field]; ok { + var formattedValue string + switch typedValue := value.(type) { + case []interface{}: + formattedValue = fmt.Sprintf("%d item(s)", len(typedValue)) + default: + formattedValue = fmt.Sprintf("%v", value) + } + if _, err := fmt.Fprintf(w, "\t%s", formattedValue); err != nil { + return err + } + } + } if _, err := fmt.Fprint(w, appendLabels(metadata.GetLabels(), options.ColumnLabels)); err != nil { return err } diff --git a/pkg/printers/humanreadable_test.go b/pkg/printers/humanreadable_test.go new file mode 100644 index 00000000000..2649ba34e23 --- /dev/null +++ b/pkg/printers/humanreadable_test.go @@ -0,0 +1,90 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package printers + +import ( + "bytes" + "regexp" + "testing" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func TestPrintUnstructuredObject(t *testing.T) { + tests := []struct { + expected string + options PrintOptions + }{ + { + expected: "NAME\\s+KIND\\s+DUMMY 1\\s+DUMMY 2\\s+ITEMS\nMyName\\s+Test\\.v1\\.\\s+present\\s+present\\s+1 item\\(s\\)", + }, + { + options: PrintOptions{ + WithNamespace: true, + }, + expected: "NAMESPACE\\s+NAME\\s+KIND\\s+DUMMY 1\\s+DUMMY 2\nMyNamespace\\s+MyName\\s+Test\\.v1\\.\\s+present\\s+present", + }, + { + options: PrintOptions{ + ShowLabels: true, + WithNamespace: true, + }, + expected: "NAMESPACE\\s+NAME\\s+KIND\\s+DUMMY 1\\s+LABELS\nMyNamespace\\s+MyName\\s+Test\\.v1\\.\\s+present\\s+", + }, + } + out := bytes.NewBuffer([]byte{}) + + obj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Test", + "dummy1": "present", + "dummy2": "present", + "metadata": map[string]interface{}{ + "name": "MyName", + "namespace": "MyNamespace", + "creationTimestamp": "2017-04-01T00:00:00Z", + "resourceVersion": 123, + "uid": "00000000-0000-0000-0000-000000000001", + "dummy3": "present", + }, + "items": []interface{}{ + map[string]interface{}{ + "itemBool": true, + "itemInt": 42, + }, + }, + "url": "http://localhost", + "status": "ok", + }, + } + + for _, test := range tests { + printer := &HumanReadablePrinter{ + options: test.options, + } + printer.PrintObj(obj, out) + + matches, err := regexp.MatchString(test.expected, out.String()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !matches { + t.Errorf("wanted %s, got %s", test.expected, out) + } + } +} diff --git a/pkg/printers/internalversion/BUILD b/pkg/printers/internalversion/BUILD index b6366fd6ffa..67ca8fddfb7 100644 --- a/pkg/printers/internalversion/BUILD +++ b/pkg/printers/internalversion/BUILD @@ -87,6 +87,8 @@ go_library( "//pkg/kubelet/qos:go_default_library", "//pkg/printers:go_default_library", "//pkg/util/node:go_default_library", + "//pkg/util/slice:go_default_library", + "//vendor/github.com/fatih/camelcase:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", diff --git a/pkg/printers/internalversion/describe.go b/pkg/printers/internalversion/describe.go index 320c77ab25b..932d17cdaa9 100644 --- a/pkg/printers/internalversion/describe.go +++ b/pkg/printers/internalversion/describe.go @@ -31,6 +31,7 @@ import ( "github.com/golang/glog" + "github.com/fatih/camelcase" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/resource" @@ -70,6 +71,7 @@ import ( "k8s.io/kubernetes/pkg/fieldpath" "k8s.io/kubernetes/pkg/kubelet/qos" "k8s.io/kubernetes/pkg/printers" + "k8s.io/kubernetes/pkg/util/slice" ) // Each level has 2 spaces for PrefixWriter @@ -198,6 +200,8 @@ func (g *genericDescriber) Describe(namespace, name string, describerSettings pr w.Write(LEVEL_0, "Name:\t%s\n", obj.GetName()) w.Write(LEVEL_0, "Namespace:\t%s\n", obj.GetNamespace()) printLabelsMultiline(w, "Labels", obj.GetLabels()) + printAnnotationsMultiline(w, "Annotations", obj.GetAnnotations()) + printUnstructuredContent(w, LEVEL_0, obj.UnstructuredContent(), "", ".metadata.name", ".metadata.namespace", ".metadata.labels", ".metadata.annotations") if events != nil { DescribeEvents(events, w) } @@ -205,6 +209,67 @@ func (g *genericDescriber) Describe(namespace, name string, describerSettings pr }) } +func printUnstructuredContent(w PrefixWriter, level int, content map[string]interface{}, skipPrefix string, skip ...string) { + fields := []string{} + for field := range content { + fields = append(fields, field) + } + sort.Strings(fields) + + for _, field := range fields { + value := content[field] + switch typedValue := value.(type) { + case map[string]interface{}: + skipExpr := fmt.Sprintf("%s.%s", skipPrefix, field) + if slice.ContainsString(skip, skipExpr, nil) { + continue + } + w.Write(level, fmt.Sprintf("%s:\n", smartLabelFor(field))) + printUnstructuredContent(w, level+1, typedValue, skipExpr, skip...) + + case []interface{}: + skipExpr := fmt.Sprintf("%s.%s", skipPrefix, field) + if slice.ContainsString(skip, skipExpr, nil) { + continue + } + w.Write(level, fmt.Sprintf("%s:\n", smartLabelFor(field))) + for _, child := range typedValue { + switch typedChild := child.(type) { + case map[string]interface{}: + printUnstructuredContent(w, level+1, typedChild, skipExpr, skip...) + default: + w.Write(level+1, fmt.Sprintf("%v\n", typedChild)) + } + } + + default: + skipExpr := fmt.Sprintf("%s.%s", skipPrefix, field) + if slice.ContainsString(skip, skipExpr, nil) { + continue + } + w.Write(level, fmt.Sprintf("%s:\t%v\n", smartLabelFor(field), typedValue)) + } + } +} + +func smartLabelFor(field string) string { + commonAcronyms := []string{"API", "URL", "UID", "OSB", "GUID"} + + splitted := camelcase.Split(field) + for i := 0; i < len(splitted); i++ { + part := splitted[i] + + if slice.ContainsString(commonAcronyms, strings.ToUpper(part), nil) { + part = strings.ToUpper(part) + } else { + part = strings.Title(part) + } + splitted[i] = part + } + + return strings.Join(splitted, " ") +} + // DefaultObjectDescriber can describe the default Kubernetes objects. var DefaultObjectDescriber printers.ObjectDescriber diff --git a/pkg/printers/internalversion/describe_test.go b/pkg/printers/internalversion/describe_test.go index 874f6db7ee9..bd7ac29b545 100644 --- a/pkg/printers/internalversion/describe_test.go +++ b/pkg/printers/internalversion/describe_test.go @@ -27,6 +27,7 @@ import ( apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/kubernetes/federation/apis/federation" fedfake "k8s.io/kubernetes/federation/client/clientset_generated/federation_internalclientset/fake" @@ -1313,3 +1314,87 @@ func TestPrintLabelsMultiline(t *testing.T) { } } } + +func TestDescribeUnstructuredContent(t *testing.T) { + testCases := []struct { + expected string + unexpected string + }{ + { + expected: `API Version: v1 +Dummy 2: present +Items: + Item Bool: true + Item Int: 42 +Kind: Test +Metadata: + Creation Timestamp: 2017-04-01T00:00:00Z + Name: MyName + Namespace: MyNamespace + Resource Version: 123 + UID: 00000000-0000-0000-0000-000000000001 +Status: ok +URL: http://localhost +`, + }, + { + unexpected: "\nDummy 1:\tpresent\n", + }, + { + unexpected: "Dummy 1", + }, + { + unexpected: "Dummy 3", + }, + { + unexpected: "Dummy3", + }, + { + unexpected: "dummy3", + }, + { + unexpected: "dummy 3", + }, + } + out := new(bytes.Buffer) + w := NewPrefixWriter(out) + obj := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Test", + "dummy1": "present", + "dummy2": "present", + "metadata": map[string]interface{}{ + "name": "MyName", + "namespace": "MyNamespace", + "creationTimestamp": "2017-04-01T00:00:00Z", + "resourceVersion": 123, + "uid": "00000000-0000-0000-0000-000000000001", + "dummy3": "present", + }, + "items": []interface{}{ + map[string]interface{}{ + "itemBool": true, + "itemInt": 42, + }, + }, + "url": "http://localhost", + "status": "ok", + }, + } + printUnstructuredContent(w, LEVEL_0, obj.UnstructuredContent(), "", ".dummy1", ".metadata.dummy3") + output := out.String() + + for _, test := range testCases { + if len(test.expected) > 0 { + if !strings.Contains(output, test.expected) { + t.Errorf("Expected to find %q in: %q", test.expected, output) + } + } + if len(test.unexpected) > 0 { + if strings.Contains(output, test.unexpected) { + t.Errorf("Didn't expect to find %q in: %q", test.unexpected, output) + } + } + } +} diff --git a/pkg/util/slice/slice.go b/pkg/util/slice/slice.go index 9520bc32aed..1b8f67c0c17 100644 --- a/pkg/util/slice/slice.go +++ b/pkg/util/slice/slice.go @@ -49,6 +49,20 @@ func ShuffleStrings(s []string) []string { return shuffled } +// ContainsString checks if a given slice of strings contains the provided string. +// If a modifier func is provided, it is called with the slice item before the comparation. +func ContainsString(slice []string, s string, modifier func(s string) string) bool { + for _, item := range slice { + if item == s { + return true + } + if modifier != nil && modifier(item) == s { + return true + } + } + return false +} + // Int64Slice attaches the methods of Interface to []int64, // sorting in increasing order. type Int64Slice []int64 diff --git a/vendor/BUILD b/vendor/BUILD index 8fe85d5ecbe..22d188f64f6 100644 --- a/vendor/BUILD +++ b/vendor/BUILD @@ -144,6 +144,7 @@ filegroup( "//vendor/github.com/emicklei/go-restful:all-srcs", "//vendor/github.com/evanphx/json-patch:all-srcs", "//vendor/github.com/exponent-io/jsonpath:all-srcs", + "//vendor/github.com/fatih/camelcase:all-srcs", "//vendor/github.com/fsnotify/fsnotify:all-srcs", "//vendor/github.com/garyburd/redigo/internal:all-srcs", "//vendor/github.com/garyburd/redigo/redis:all-srcs", diff --git a/vendor/github.com/fatih/camelcase/.travis.yml b/vendor/github.com/fatih/camelcase/.travis.yml new file mode 100644 index 00000000000..0244e464e6d --- /dev/null +++ b/vendor/github.com/fatih/camelcase/.travis.yml @@ -0,0 +1,3 @@ +language: go +go: 1.4 + diff --git a/vendor/github.com/fatih/camelcase/BUILD b/vendor/github.com/fatih/camelcase/BUILD new file mode 100644 index 00000000000..d63414b1385 --- /dev/null +++ b/vendor/github.com/fatih/camelcase/BUILD @@ -0,0 +1,27 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = ["camelcase.go"], + tags = ["automanaged"], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/vendor/github.com/fatih/camelcase/LICENSE.md b/vendor/github.com/fatih/camelcase/LICENSE.md new file mode 100644 index 00000000000..aa4a536caf0 --- /dev/null +++ b/vendor/github.com/fatih/camelcase/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2015 Fatih Arslan + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/fatih/camelcase/README.md b/vendor/github.com/fatih/camelcase/README.md new file mode 100644 index 00000000000..105a6ae33de --- /dev/null +++ b/vendor/github.com/fatih/camelcase/README.md @@ -0,0 +1,58 @@ +# CamelCase [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/fatih/camelcase) [![Build Status](http://img.shields.io/travis/fatih/camelcase.svg?style=flat-square)](https://travis-ci.org/fatih/camelcase) + +CamelCase is a Golang (Go) package to split the words of a camelcase type +string into a slice of words. It can be used to convert a camelcase word (lower +or upper case) into any type of word. + +## Splitting rules: + +1. If string is not valid UTF-8, return it without splitting as + single item array. +2. Assign all unicode characters into one of 4 sets: lower case + letters, upper case letters, numbers, and all other characters. +3. Iterate through characters of string, introducing splits + between adjacent characters that belong to different sets. +4. Iterate through array of split strings, and if a given string + is upper case: + * if subsequent string is lower case: + * move last character of upper case string to beginning of + lower case string + +## Install + +```bash +go get github.com/fatih/camelcase +``` + +## Usage and examples + +```go +splitted := camelcase.Split("GolangPackage") + +fmt.Println(splitted[0], splitted[1]) // prints: "Golang", "Package" +``` + +Both lower camel case and upper camel case are supported. For more info please +check: [http://en.wikipedia.org/wiki/CamelCase](http://en.wikipedia.org/wiki/CamelCase) + +Below are some example cases: + +``` +"" => [] +"lowercase" => ["lowercase"] +"Class" => ["Class"] +"MyClass" => ["My", "Class"] +"MyC" => ["My", "C"] +"HTML" => ["HTML"] +"PDFLoader" => ["PDF", "Loader"] +"AString" => ["A", "String"] +"SimpleXMLParser" => ["Simple", "XML", "Parser"] +"vimRPCPlugin" => ["vim", "RPC", "Plugin"] +"GL11Version" => ["GL", "11", "Version"] +"99Bottles" => ["99", "Bottles"] +"May5" => ["May", "5"] +"BFG9000" => ["BFG", "9000"] +"BöseÜberraschung" => ["Böse", "Überraschung"] +"Two spaces" => ["Two", " ", "spaces"] +"BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"] +``` diff --git a/vendor/github.com/fatih/camelcase/camelcase.go b/vendor/github.com/fatih/camelcase/camelcase.go new file mode 100644 index 00000000000..02160c9a435 --- /dev/null +++ b/vendor/github.com/fatih/camelcase/camelcase.go @@ -0,0 +1,90 @@ +// Package camelcase is a micro package to split the words of a camelcase type +// string into a slice of words. +package camelcase + +import ( + "unicode" + "unicode/utf8" +) + +// Split splits the camelcase word and returns a list of words. It also +// supports digits. Both lower camel case and upper camel case are supported. +// For more info please check: http://en.wikipedia.org/wiki/CamelCase +// +// Examples +// +// "" => [""] +// "lowercase" => ["lowercase"] +// "Class" => ["Class"] +// "MyClass" => ["My", "Class"] +// "MyC" => ["My", "C"] +// "HTML" => ["HTML"] +// "PDFLoader" => ["PDF", "Loader"] +// "AString" => ["A", "String"] +// "SimpleXMLParser" => ["Simple", "XML", "Parser"] +// "vimRPCPlugin" => ["vim", "RPC", "Plugin"] +// "GL11Version" => ["GL", "11", "Version"] +// "99Bottles" => ["99", "Bottles"] +// "May5" => ["May", "5"] +// "BFG9000" => ["BFG", "9000"] +// "BöseÜberraschung" => ["Böse", "Überraschung"] +// "Two spaces" => ["Two", " ", "spaces"] +// "BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"] +// +// Splitting rules +// +// 1) If string is not valid UTF-8, return it without splitting as +// single item array. +// 2) Assign all unicode characters into one of 4 sets: lower case +// letters, upper case letters, numbers, and all other characters. +// 3) Iterate through characters of string, introducing splits +// between adjacent characters that belong to different sets. +// 4) Iterate through array of split strings, and if a given string +// is upper case: +// if subsequent string is lower case: +// move last character of upper case string to beginning of +// lower case string +func Split(src string) (entries []string) { + // don't split invalid utf8 + if !utf8.ValidString(src) { + return []string{src} + } + entries = []string{} + var runes [][]rune + lastClass := 0 + class := 0 + // split into fields based on class of unicode character + for _, r := range src { + switch true { + case unicode.IsLower(r): + class = 1 + case unicode.IsUpper(r): + class = 2 + case unicode.IsDigit(r): + class = 3 + default: + class = 4 + } + if class == lastClass { + runes[len(runes)-1] = append(runes[len(runes)-1], r) + } else { + runes = append(runes, []rune{r}) + } + lastClass = class + } + // handle upper case -> lower case sequences, e.g. + // "PDFL", "oader" -> "PDF", "Loader" + for i := 0; i < len(runes)-1; i++ { + if unicode.IsUpper(runes[i][0]) && unicode.IsLower(runes[i+1][0]) { + runes[i+1] = append([]rune{runes[i][len(runes[i])-1]}, runes[i+1]...) + runes[i] = runes[i][:len(runes[i])-1] + } + } + // construct []string from results + for _, s := range runes { + if len(s) > 0 { + entries = append(entries, string(s)) + } + } + return +}