1/*
2Copyright (c) 2014-2016 VMware, Inc. All Rights Reserved.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package cli
18
19import (
20	"context"
21	"flag"
22	"fmt"
23	"io"
24	"io/ioutil"
25	"os"
26	"sort"
27	"text/tabwriter"
28
29	"github.com/vmware/govmomi/vim25/types"
30)
31
32type HasFlags interface {
33	// Register may be called more than once and should be idempotent.
34	Register(ctx context.Context, f *flag.FlagSet)
35
36	// Process may be called more than once and should be idempotent.
37	Process(ctx context.Context) error
38}
39
40type Command interface {
41	HasFlags
42
43	Run(ctx context.Context, f *flag.FlagSet) error
44}
45
46func generalHelp(w io.Writer) {
47	fmt.Fprintf(w, "Usage of %s:\n", os.Args[0])
48
49	cmds := []string{}
50	for name := range commands {
51		cmds = append(cmds, name)
52	}
53
54	sort.Strings(cmds)
55
56	for _, name := range cmds {
57		fmt.Fprintf(w, "  %s\n", name)
58	}
59}
60
61func commandHelp(w io.Writer, name string, cmd Command, f *flag.FlagSet) {
62	type HasUsage interface {
63		Usage() string
64	}
65
66	fmt.Fprintf(w, "Usage: %s %s [OPTIONS]", os.Args[0], name)
67	if u, ok := cmd.(HasUsage); ok {
68		fmt.Fprintf(w, " %s", u.Usage())
69	}
70	fmt.Fprintf(w, "\n")
71
72	type HasDescription interface {
73		Description() string
74	}
75
76	if u, ok := cmd.(HasDescription); ok {
77		fmt.Fprintf(w, "\n%s\n", u.Description())
78	}
79
80	n := 0
81	f.VisitAll(func(_ *flag.Flag) {
82		n += 1
83	})
84
85	if n > 0 {
86		fmt.Fprintf(w, "\nOptions:\n")
87		tw := tabwriter.NewWriter(w, 2, 0, 2, ' ', 0)
88		f.VisitAll(func(f *flag.Flag) {
89			fmt.Fprintf(tw, "\t-%s=%s\t%s\n", f.Name, f.DefValue, f.Usage)
90		})
91		tw.Flush()
92	}
93}
94
95func clientLogout(ctx context.Context, cmd Command) error {
96	type logout interface {
97		Logout(context.Context) error
98	}
99
100	if l, ok := cmd.(logout); ok {
101		return l.Logout(ctx)
102	}
103
104	return nil
105}
106
107func Run(args []string) int {
108	hw := os.Stderr
109	rc := 1
110	hwrc := func(arg string) {
111		if arg == "-h" {
112			hw = os.Stdout
113			rc = 0
114		}
115	}
116
117	var err error
118
119	if len(args) == 0 {
120		generalHelp(hw)
121		return rc
122	}
123
124	// Look up real command name in aliases table.
125	name, ok := aliases[args[0]]
126	if !ok {
127		name = args[0]
128	}
129
130	cmd, ok := commands[name]
131	if !ok {
132		hwrc(name)
133		generalHelp(hw)
134		return rc
135	}
136
137	fs := flag.NewFlagSet("", flag.ContinueOnError)
138	fs.SetOutput(ioutil.Discard)
139
140	ctx := context.Background()
141
142	if id := os.Getenv("GOVC_OPERATION_ID"); id != "" {
143		ctx = context.WithValue(ctx, types.ID{}, id)
144	}
145
146	cmd.Register(ctx, fs)
147
148	if err = fs.Parse(args[1:]); err != nil {
149		goto error
150	}
151
152	if err = cmd.Process(ctx); err != nil {
153		goto error
154	}
155
156	if err = cmd.Run(ctx, fs); err != nil {
157		goto error
158	}
159
160	if err = clientLogout(ctx, cmd); err != nil {
161		goto error
162	}
163
164	return 0
165
166error:
167	if err == flag.ErrHelp {
168		if len(args) == 2 {
169			hwrc(args[1])
170		}
171		commandHelp(hw, args[0], cmd, fs)
172	} else {
173		fmt.Fprintf(os.Stderr, "%s: %s\n", os.Args[0], err)
174	}
175
176	_ = clientLogout(ctx, cmd)
177
178	return rc
179}
180