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
[]()
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 @@
+
+
+
+
+
+
+
+
+
+
+
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
+
+
+[]()
+
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
+}