From e8b9dde6230ec1297aa425d4b9a49705121a5a64 Mon Sep 17 00:00:00 2001 From: Anastasis Andronidis Date: Thu, 13 Aug 2015 03:33:25 +0200 Subject: [PATCH] new kubectl explain command --- .generated_docs | 2 + contrib/completions/bash/kubectl | 17 ++ docs/man/man1/.files_generated | 1 + docs/man/man1/kubectl-explain.1 | 147 ++++++++++++ docs/man/man1/kubectl.1 | 2 +- docs/user-guide/kubectl/.files_generated | 1 + docs/user-guide/kubectl/kubectl.md | 3 +- docs/user-guide/kubectl/kubectl_explain.md | 104 +++++++++ hack/verify-flags/known-flags.txt | 1 + pkg/kubectl/cmd/cmd.go | 1 + pkg/kubectl/cmd/explain.go | 85 +++++++ pkg/kubectl/explain.go | 260 +++++++++++++++++++++ 12 files changed, 622 insertions(+), 2 deletions(-) create mode 100644 docs/man/man1/kubectl-explain.1 create mode 100644 docs/user-guide/kubectl/kubectl_explain.md create mode 100644 pkg/kubectl/cmd/explain.go create mode 100644 pkg/kubectl/explain.go diff --git a/.generated_docs b/.generated_docs index b61bb953990..e29e1992344 100644 --- a/.generated_docs +++ b/.generated_docs @@ -17,6 +17,7 @@ docs/man/man1/kubectl-delete.1 docs/man/man1/kubectl-describe.1 docs/man/man1/kubectl-edit.1 docs/man/man1/kubectl-exec.1 +docs/man/man1/kubectl-explain.1 docs/man/man1/kubectl-expose.1 docs/man/man1/kubectl-get.1 docs/man/man1/kubectl-label.1 @@ -50,6 +51,7 @@ docs/user-guide/kubectl/kubectl_delete.md docs/user-guide/kubectl/kubectl_describe.md docs/user-guide/kubectl/kubectl_edit.md docs/user-guide/kubectl/kubectl_exec.md +docs/user-guide/kubectl/kubectl_explain.md docs/user-guide/kubectl/kubectl_expose.md docs/user-guide/kubectl/kubectl_get.md docs/user-guide/kubectl/kubectl_label.md diff --git a/contrib/completions/bash/kubectl b/contrib/completions/bash/kubectl index d7ebe9763f0..8c05586fa31 100644 --- a/contrib/completions/bash/kubectl +++ b/contrib/completions/bash/kubectl @@ -1105,6 +1105,22 @@ _kubectl_version() must_have_one_noun=() } +_kubectl_explain() +{ + last_command="kubectl_explain" + commands=() + + flags=() + two_word_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--recursive") + + must_have_one_flag=() + must_have_one_noun=() +} + _kubectl() { last_command="kubectl" @@ -1133,6 +1149,7 @@ _kubectl() commands+=("cluster-info") commands+=("api-versions") commands+=("version") + commands+=("explain") flags=() two_word_flags=() diff --git a/docs/man/man1/.files_generated b/docs/man/man1/.files_generated index 078d014bcf5..68d2728c716 100644 --- a/docs/man/man1/.files_generated +++ b/docs/man/man1/.files_generated @@ -15,6 +15,7 @@ kubectl-delete.1 kubectl-describe.1 kubectl-edit.1 kubectl-exec.1 +kubectl-explain.1 kubectl-expose.1 kubectl-get.1 kubectl-label.1 diff --git a/docs/man/man1/kubectl-explain.1 b/docs/man/man1/kubectl-explain.1 new file mode 100644 index 00000000000..4a477041fa2 --- /dev/null +++ b/docs/man/man1/kubectl-explain.1 @@ -0,0 +1,147 @@ +.TH "KUBERNETES" "1" " kubernetes User Manuals" "Eric Paris" "Jan 2015" "" + + +.SH NAME +.PP +kubectl explain \- Documentation of resources. + + +.SH SYNOPSIS +.PP +\fBkubectl explain\fP [OPTIONS] + + +.SH DESCRIPTION +.PP +Documentation of resources. + +.PP +Possible resource types include: pods (po), services (svc), +replicationcontrollers (rc), nodes (no), events (ev), componentstatuses (cs), +limitranges (limits), persistentvolumes (pv), persistentvolumeclaims (pvc), +resourcequotas (quota), namespaces (ns) or endpoints (ep). + + +.SH OPTIONS +.PP +\fB\-\-recursive\fP=false + Print the fields of fields (Currently only 1 level deep) + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-alsologtostderr\fP=false + log to standard error as well as files + +.PP +\fB\-\-api\-version\fP="" + The API version to use when talking to the server + +.PP +\fB\-\-certificate\-authority\fP="" + Path to a cert. file for the certificate authority. + +.PP +\fB\-\-client\-certificate\fP="" + Path to a client key file for TLS. + +.PP +\fB\-\-client\-key\fP="" + Path to a client key file for TLS. + +.PP +\fB\-\-cluster\fP="" + The name of the kubeconfig cluster to use + +.PP +\fB\-\-context\fP="" + The name of the kubeconfig context to use + +.PP +\fB\-\-insecure\-skip\-tls\-verify\fP=false + If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. + +.PP +\fB\-\-kubeconfig\fP="" + Path to the kubeconfig file to use for CLI requests. + +.PP +\fB\-\-log\-backtrace\-at\fP=:0 + when logging hits line file:N, emit a stack trace + +.PP +\fB\-\-log\-dir\fP="" + If non\-empty, write log files in this directory + +.PP +\fB\-\-log\-flush\-frequency\fP=5s + Maximum number of seconds between log flushes + +.PP +\fB\-\-logtostderr\fP=true + log to standard error instead of files + +.PP +\fB\-\-match\-server\-version\fP=false + Require server version to match client version + +.PP +\fB\-\-namespace\fP="" + If present, the namespace scope for this CLI request. + +.PP +\fB\-\-password\fP="" + Password for basic authentication to the API server. + +.PP +\fB\-s\fP, \fB\-\-server\fP="" + The address and port of the Kubernetes API server + +.PP +\fB\-\-stderrthreshold\fP=2 + logs at or above this threshold go to stderr + +.PP +\fB\-\-token\fP="" + Bearer token for authentication to the API server. + +.PP +\fB\-\-user\fP="" + The name of the kubeconfig user to use + +.PP +\fB\-\-username\fP="" + Username for basic authentication to the API server. + +.PP +\fB\-\-v\fP=0 + log level for V logs + +.PP +\fB\-\-vmodule\fP= + comma\-separated list of pattern=N settings for file\-filtered logging + + +.SH EXAMPLE +.PP +.RS + +.nf +# Get the documentation of the resource and its fields +$ kubectl explain pods + +# Get the documentation of a specific field of a resource +$ kubectl explain pods.spec.containers + +.fi +.RE + + +.SH SEE ALSO +.PP +\fBkubectl(1)\fP, + + +.SH HISTORY +.PP +January 2015, Originally compiled by Eric Paris (eparis at redhat dot com) based on the kubernetes source material, but hopefully they have been automatically generated since! diff --git a/docs/man/man1/kubectl.1 b/docs/man/man1/kubectl.1 index 357b51a6601..c2acd8202d4 100644 --- a/docs/man/man1/kubectl.1 +++ b/docs/man/man1/kubectl.1 @@ -116,7 +116,7 @@ Find more information at .SH SEE ALSO .PP -\fBkubectl\-get(1)\fP, \fBkubectl\-describe(1)\fP, \fBkubectl\-create(1)\fP, \fBkubectl\-replace(1)\fP, \fBkubectl\-patch(1)\fP, \fBkubectl\-delete(1)\fP, \fBkubectl\-edit(1)\fP, \fBkubectl\-namespace(1)\fP, \fBkubectl\-logs(1)\fP, \fBkubectl\-rolling\-update(1)\fP, \fBkubectl\-scale(1)\fP, \fBkubectl\-attach(1)\fP, \fBkubectl\-exec(1)\fP, \fBkubectl\-port\-forward(1)\fP, \fBkubectl\-proxy(1)\fP, \fBkubectl\-run(1)\fP, \fBkubectl\-stop(1)\fP, \fBkubectl\-expose(1)\fP, \fBkubectl\-label(1)\fP, \fBkubectl\-annotate(1)\fP, \fBkubectl\-config(1)\fP, \fBkubectl\-cluster\-info(1)\fP, \fBkubectl\-api\-versions(1)\fP, \fBkubectl\-version(1)\fP, +\fBkubectl\-get(1)\fP, \fBkubectl\-describe(1)\fP, \fBkubectl\-create(1)\fP, \fBkubectl\-replace(1)\fP, \fBkubectl\-patch(1)\fP, \fBkubectl\-delete(1)\fP, \fBkubectl\-edit(1)\fP, \fBkubectl\-namespace(1)\fP, \fBkubectl\-logs(1)\fP, \fBkubectl\-rolling\-update(1)\fP, \fBkubectl\-scale(1)\fP, \fBkubectl\-attach(1)\fP, \fBkubectl\-exec(1)\fP, \fBkubectl\-port\-forward(1)\fP, \fBkubectl\-proxy(1)\fP, \fBkubectl\-run(1)\fP, \fBkubectl\-stop(1)\fP, \fBkubectl\-expose(1)\fP, \fBkubectl\-label(1)\fP, \fBkubectl\-annotate(1)\fP, \fBkubectl\-config(1)\fP, \fBkubectl\-cluster\-info(1)\fP, \fBkubectl\-api\-versions(1)\fP, \fBkubectl\-version(1)\fP, \fBkubectl\-explain(1)\fP, .SH HISTORY diff --git a/docs/user-guide/kubectl/.files_generated b/docs/user-guide/kubectl/.files_generated index 52fd90016a5..3ebfe4d6c6d 100644 --- a/docs/user-guide/kubectl/.files_generated +++ b/docs/user-guide/kubectl/.files_generated @@ -16,6 +16,7 @@ kubectl_delete.md kubectl_describe.md kubectl_edit.md kubectl_exec.md +kubectl_explain.md kubectl_expose.md kubectl_get.md kubectl_label.md diff --git a/docs/user-guide/kubectl/kubectl.md b/docs/user-guide/kubectl/kubectl.md index 58631bbb75b..2d1b75b976d 100644 --- a/docs/user-guide/kubectl/kubectl.md +++ b/docs/user-guide/kubectl/kubectl.md @@ -86,6 +86,7 @@ kubectl * [kubectl describe](kubectl_describe.md) - Show details of a specific resource or group of resources * [kubectl edit](kubectl_edit.md) - Edit a resource on the server * [kubectl exec](kubectl_exec.md) - Execute a command in a container. +* [kubectl explain](kubectl_explain.md) - Documentation of resources. * [kubectl expose](kubectl_expose.md) - Take a replication controller, service or pod and expose it as a new Kubernetes Service * [kubectl get](kubectl_get.md) - Display one or many resources * [kubectl label](kubectl_label.md) - Update the labels on a resource @@ -101,7 +102,7 @@ kubectl * [kubectl stop](kubectl_stop.md) - Deprecated: Gracefully shut down a resource by name or filename. * [kubectl version](kubectl_version.md) - Print the client and server version information. -###### Auto generated by spf13/cobra at 2015-09-11 06:17:55.670147499 +0000 UTC +###### Auto generated by spf13/cobra at 2015-09-22 11:13:47.6353025 +0000 UTC [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl.md?pixel)]() diff --git a/docs/user-guide/kubectl/kubectl_explain.md b/docs/user-guide/kubectl/kubectl_explain.md new file mode 100644 index 00000000000..496d3e2a7cc --- /dev/null +++ b/docs/user-guide/kubectl/kubectl_explain.md @@ -0,0 +1,104 @@ + + + + +WARNING +WARNING +WARNING +WARNING +WARNING + +

PLEASE NOTE: This document applies to the HEAD of the source tree

+ +If you are using a released version of Kubernetes, you should +refer to the docs that go with that version. + + +The latest 1.0.x release of this document can be found +[here](http://releases.k8s.io/release-1.0/docs/user-guide/kubectl/kubectl_explain.md). + +Documentation for other releases can be found at +[releases.k8s.io](http://releases.k8s.io). + +-- + + + + + +## kubectl explain + +Documentation of resources. + +### Synopsis + + +Documentation of resources. + +Possible resource types include: pods (po), services (svc), +replicationcontrollers (rc), nodes (no), events (ev), componentstatuses (cs), +limitranges (limits), persistentvolumes (pv), persistentvolumeclaims (pvc), +resourcequotas (quota), namespaces (ns) or endpoints (ep). + +``` +kubectl explain RESOURCE +``` + +### Examples + +``` +# Get the documentation of the resource and its fields +$ kubectl explain pods + +# Get the documentation of a specific field of a resource +$ kubectl explain pods.spec.containers +``` + +### Options + +``` + --recursive[=false]: Print the fields of fields (Currently only 1 level deep) +``` + +### Options inherited from parent commands + +``` + --alsologtostderr[=false]: log to standard error as well as files + --api-version="": The API version to use when talking to the server + --certificate-authority="": Path to a cert. file for the certificate authority. + --client-certificate="": Path to a client key file for TLS. + --client-key="": Path to a client key file for TLS. + --cluster="": The name of the kubeconfig cluster to use + --context="": The name of the kubeconfig context to use + --insecure-skip-tls-verify[=false]: If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. + --kubeconfig="": Path to the kubeconfig file to use for CLI requests. + --log-backtrace-at=:0: when logging hits line file:N, emit a stack trace + --log-dir="": If non-empty, write log files in this directory + --log-flush-frequency=5s: Maximum number of seconds between log flushes + --logtostderr[=true]: log to standard error instead of files + --match-server-version[=false]: Require server version to match client version + --namespace="": If present, the namespace scope for this CLI request. + --password="": Password for basic authentication to the API server. + -s, --server="": The address and port of the Kubernetes API server + --stderrthreshold=2: logs at or above this threshold go to stderr + --token="": Bearer token for authentication to the API server. + --user="": The name of the kubeconfig user to use + --username="": Username for basic authentication to the API server. + --v=0: log level for V logs + --vmodule=: comma-separated list of pattern=N settings for file-filtered logging +``` + +### SEE ALSO + +* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager + +###### Auto generated by spf13/cobra at 2015-09-25 11:07:41.758531939 +0000 UTC + + +[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_explain.md?pixel)]() + diff --git a/hack/verify-flags/known-flags.txt b/hack/verify-flags/known-flags.txt index cc4b2b16539..9789ae98281 100644 --- a/hack/verify-flags/known-flags.txt +++ b/hack/verify-flags/known-flags.txt @@ -190,6 +190,7 @@ node-startup-grace-period node-status-update-frequency node-sync-period no-headers +no-suggestions num-nodes oidc-ca-file oidc-client-id diff --git a/pkg/kubectl/cmd/cmd.go b/pkg/kubectl/cmd/cmd.go index af6aa742ddc..29958e988db 100644 --- a/pkg/kubectl/cmd/cmd.go +++ b/pkg/kubectl/cmd/cmd.go @@ -172,6 +172,7 @@ Find more information at https://github.com/kubernetes/kubernetes.`, cmds.AddCommand(NewCmdClusterInfo(f, out)) cmds.AddCommand(NewCmdApiVersions(f, out)) cmds.AddCommand(NewCmdVersion(f, out)) + cmds.AddCommand(NewCmdExplain(f, out)) return cmds } diff --git a/pkg/kubectl/cmd/explain.go b/pkg/kubectl/cmd/explain.go new file mode 100644 index 00000000000..15053964b0f --- /dev/null +++ b/pkg/kubectl/cmd/explain.go @@ -0,0 +1,85 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 cmd + +import ( + "io" + + "github.com/spf13/cobra" + + "k8s.io/kubernetes/pkg/kubectl" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" +) + +const ( + explainExamples = `# Get the documentation of the resource and its fields +$ kubectl explain pods + +# Get the documentation of a specific field of a resource +$ kubectl explain pods.spec.containers` + + explainLong = `Documentation of resources. + +Possible resource types include: pods (po), services (svc), +replicationcontrollers (rc), nodes (no), events (ev), componentstatuses (cs), +limitranges (limits), persistentvolumes (pv), persistentvolumeclaims (pvc), +resourcequotas (quota), namespaces (ns) or endpoints (ep).` +) + +// NewCmdExplain returns a cobra command for swagger docs +func NewCmdExplain(f *cmdutil.Factory, out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "explain RESOURCE", + Short: "Documentation of resources.", + Long: explainLong, + Example: explainExamples, + Run: func(cmd *cobra.Command, args []string) { + err := RunExplain(f, out, cmd, args) + cmdutil.CheckErr(err) + }, + } + cmd.Flags().Bool("recursive", false, "Print the fields of fields (Currently only 1 level deep)") + return cmd +} + +// RunExplain executes the appropriate steps to print a model's documentation +func RunExplain(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return cmdutil.UsageError(cmd, "We accept only this format: explain RESOURCE") + } + + client, err := f.Client() + if err != nil { + return err + } + + recursive := cmdutil.GetFlagBool(cmd, "recursive") + apiV := cmdutil.GetFlagString(cmd, "api-version") + + swagSchema, err := kubectl.GetSwaggerSchema(apiV, client) + if err != nil { + return err + } + + mapper, _ := f.Object() + inModel, fieldsPath, err := kubectl.SplitAndParseResourceRequest(args[0], mapper) + if err != nil { + return err + } + + return kubectl.PrintModelDescription(inModel, fieldsPath, out, swagSchema, recursive) +} diff --git a/pkg/kubectl/explain.go b/pkg/kubectl/explain.go new file mode 100644 index 00000000000..087c217c55d --- /dev/null +++ b/pkg/kubectl/explain.go @@ -0,0 +1,260 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 kubectl + +import ( + "fmt" + "io" + "strings" + + "github.com/emicklei/go-restful/swagger" + + "k8s.io/kubernetes/pkg/api/meta" + client "k8s.io/kubernetes/pkg/client/unversioned" +) + +var allModels = make(map[string]*swagger.NamedModel) +var recursive = false // this is global for convenience, can become int for multiple levels + +// GetSwaggerSchema returns the swagger spec from master +func GetSwaggerSchema(apiVer string, kubeClient client.Interface) (*swagger.ApiDeclaration, error) { + swaggerSchema, err := kubeClient.SwaggerSchema(apiVer) + if err != nil { + return nil, fmt.Errorf("couldn't read swagger schema from server: %v", err) + } + return swaggerSchema, nil +} + +// SplitAndParseResourceRequest separates the users input into a model and fields +func SplitAndParseResourceRequest(inResource string, mapper meta.RESTMapper) (string, []string, error) { + inResource, fieldsPath := splitDotNotation(inResource) + inResource, _ = mapper.ResourceSingularizer(expandResourceShortcut(inResource)) + return inResource, fieldsPath, nil +} + +// PrintModelDescription prints the description of a specific model or dot path +func PrintModelDescription(inModel string, fieldsPath []string, w io.Writer, swaggerSchema *swagger.ApiDeclaration, r bool) error { + recursive = r // this is global for convenience + apiVer := swaggerSchema.ApiVersion + "." + + var pointedModel *swagger.NamedModel + for i := range swaggerSchema.Models.List { + name := swaggerSchema.Models.List[i].Name + + allModels[name] = &swaggerSchema.Models.List[i] + if strings.ToLower(name) == strings.ToLower(apiVer+inModel) { + pointedModel = &swaggerSchema.Models.List[i] + } + } + if pointedModel == nil { + return fmt.Errorf("Requested resourse: %s doesn't exit", inModel) + } + + if len(fieldsPath) == 0 { + return printTopLevelResourceInfo(w, pointedModel) + } + + var pointedModelAsProp *swagger.NamedModelProperty + for _, field := range fieldsPath { + if prop, nextModel, isModel := getField(pointedModel, field); prop != nil { + if isModel { + pointedModelAsProp = prop + pointedModel = allModels[nextModel] + } else { + return printPrimitive(w, prop) + } + } else { + return fmt.Errorf("field: %s doesn't exist", field) + } + } + return printModelInfo(w, pointedModel, pointedModelAsProp) +} + +func splitDotNotation(model string) (string, []string) { + var fieldsPath []string + dotModel := strings.Split(model, ".") + if len(dotModel) >= 1 { + fieldsPath = dotModel[1:] + } + return dotModel[0], fieldsPath +} + +func getPointedModel(prop *swagger.ModelProperty) (string, bool) { + if prop.Ref != nil { + return *prop.Ref, true + } else if *prop.Type == "array" && prop.Items.Ref != nil { + return *prop.Items.Ref, true + } + return "", false +} + +func getField(model *swagger.NamedModel, sField string) (*swagger.NamedModelProperty, string, bool) { + for _, prop := range model.Model.Properties.List { + if prop.Name == sField { + pointedModel, isModel := getPointedModel(&prop.Property) + return &prop, pointedModel, isModel + } + } + return nil, "", false +} + +func printModelInfo(w io.Writer, model *swagger.NamedModel, modelProp *swagger.NamedModelProperty) error { + t, _ := getFieldType(&modelProp.Property) + fmt.Fprintf(w, "RESOURCE: %s <%s>\n\n", modelProp.Name, t) + fieldDesc, _ := wrapAndIndentText(modelProp.Property.Description, " ", 80) + fmt.Fprintf(w, "DESCRIPTION:\n%s\n\n%s\n", fieldDesc, indentText(model.Model.Description, " ")) + return printFields(w, model) +} + +func printPrimitive(w io.Writer, field *swagger.NamedModelProperty) error { + t, _ := getFieldType(&field.Property) + fmt.Fprintf(w, "FIELD: %s <%s>\n\n", field.Name, t) + d, _ := wrapAndIndentText(field.Property.Description, " ", 80) + fmt.Fprintf(w, "DESCRIPTION:\n%s\n", d) + return nil +} + +func printTopLevelResourceInfo(w io.Writer, model *swagger.NamedModel) error { + fmt.Fprintf(w, "DESCRIPTION:\n%s\n", model.Model.Description) + return printFields(w, model) +} + +func printFields(w io.Writer, model *swagger.NamedModel) error { + fmt.Fprint(w, "\nFIELDS:\n") + for _, field := range model.Model.Properties.List { + fieldType, err := getFieldType(&field.Property) + if err != nil { + return err + } + + if arrayContains(model.Model.Required, field.Name) { + fmt.Fprintf(w, " %s\t<%s> -required-\n", field.Name, fieldType) + } else { + fmt.Fprintf(w, " %s\t<%s>\n", field.Name, fieldType) + } + + if recursive { + pointedModel, isModel := getPointedModel(&field.Property) + if isModel { + for _, nestedField := range allModels[pointedModel].Model.Properties.List { + t, _ := getFieldType(&nestedField.Property) + fmt.Fprintf(w, " %s\t<%s>\n", nestedField.Name, t) + } + } + } else { + fieldDesc, _ := wrapAndIndentText(field.Property.Description, " ", 80) + fmt.Fprintf(w, "%s\n\n", fieldDesc) + } + } + fmt.Fprint(w, "\n") + return nil +} + +func getFieldType(prop *swagger.ModelProperty) (string, error) { + if prop.Type == nil { + return "Object", nil + } else if *prop.Type == "any" { + // Swagger Spec doesn't return information for maps. + return "map[string]string", nil + } else if *prop.Type == "array" { + if prop.Items == nil { + return "", fmt.Errorf("error in swagger spec. Property: %v contains an array without type", prop) + } + if prop.Items.Ref != nil { + fieldType := "[]Object" + return fieldType, nil + } + fieldType := "[]" + *prop.Items.Type + return fieldType, nil + } + return *prop.Type, nil +} + +func wrapAndIndentText(desc, indent string, lim int) (string, error) { + words := strings.Split(strings.Replace(strings.TrimSpace(desc), "\n", " ", -1), " ") + n := len(words) + + for i := 0; i < n; i++ { + if len(words[i]) > lim { + if strings.Contains(words[i], "/") { + s := breakURL(words[i]) + words = append(words[:i], append(s, words[i+1:]...)...) + i = i + len(s) - 1 + } else { + fmt.Println(len(words[i])) + return "", fmt.Errorf("there are words longer that the break limit is") + } + } + } + + var lines []string + line := []string{indent} + lineL := len(indent) + for i := 0; i < len(words); i++ { + w := words[i] + + if strings.HasSuffix(w, "/") && lineL+len(w)-1 < lim { + prev := line[len(line)-1] + if strings.HasSuffix(prev, "/") { + if i+1 < len(words)-1 && !strings.HasSuffix(words[i+1], "/") { + w = strings.TrimSuffix(w, "/") + } + + line[len(line)-1] = prev + w + lineL += len(w) + } else { + line = append(line, w) + lineL += len(w) + 1 + } + } else if lineL+len(w) < lim { + line = append(line, w) + lineL += len(w) + 1 + } else { + lines = append(lines, strings.Join(line, " ")) + line = []string{indent, w} + lineL = len(indent) + len(w) + } + } + lines = append(lines, strings.Join(line, " ")) + + return strings.Join(lines, "\n"), nil +} + +func breakURL(url string) []string { + var buf []string + for _, part := range strings.Split(url, "/") { + buf = append(buf, part+"/") + } + return buf +} + +func indentText(text, indent string) string { + lines := strings.Split(text, "\n") + for i := range lines { + lines[i] = indent + lines[i] + } + return strings.Join(lines, "\n") +} + +func arrayContains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +}