From 1b5ceb570a73cbb16d3edd80aa4f9f36554a6864 Mon Sep 17 00:00:00 2001 From: Fabian Reinartz Date: Thu, 18 Jun 2015 11:33:45 +0200 Subject: [PATCH] util/cli: create CLI utility package --- util/cli/cli.go | 168 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 util/cli/cli.go diff --git a/util/cli/cli.go b/util/cli/cli.go new file mode 100644 index 000000000..063db561a --- /dev/null +++ b/util/cli/cli.go @@ -0,0 +1,168 @@ +// Copyright 2015 The Prometheus 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 cli + +import ( + "bytes" + "fmt" + "io" + "sort" + "strings" + "text/template" +) + +// Command represents a single command within an application. +type Command struct { + Desc string + Run func(t Term, args ...string) int +} + +// Term handles an application's output. +type Term interface { + Infof(format string, v ...interface{}) + Errorf(format string, v ...interface{}) + Out(format string) +} + +type basicTerm struct { + out, err io.Writer +} + +// Infof implements Term. +func (t *basicTerm) Infof(format string, v ...interface{}) { + fmt.Fprintf(t.err, format, v...) + fmt.Fprint(t.err, "\n") +} + +// Errorf implements Term. +func (t *basicTerm) Errorf(format string, v ...interface{}) { + fmt.Fprintf(t.err, format, v...) + fmt.Fprint(t.err, "\n") +} + +// Out implements Term. +func (t *basicTerm) Out(msg string) { + fmt.Fprint(t.out, msg) + fmt.Fprint(t.out, "\n") +} + +// BasicTerm returns a Term writing Infof and Errorf to err and Out to out. +func BasicTerm(out, err io.Writer) Term { + return &basicTerm{out: out, err: err} +} + +// App represents an application that may consist of multiple commands. +type App struct { + Name string + Help func() string + + commands map[string]*Command +} + +// NewApp creates a new application with a pre-registered help command. +func NewApp(name string) *App { + app := &App{ + Name: name, + commands: map[string]*Command{}, + } + app.Register("help", &Command{ + Desc: "prints this help text", + Run: func(t Term, _ ...string) int { + help := app.Help + if help == nil { + help = BasicHelp(app, tmpl) + } + t.Infof(help() + "\n") + return 0 + }, + }) + return app +} + +// Register adds a new command to the application. +func (app *App) Register(name string, cmd *Command) { + name = strings.TrimSpace(name) + if name == "" { + panic("command name must not be empty") + } + if _, ok := app.commands[name]; ok { + panic("command cannot be registered twice") + } + app.commands[name] = cmd +} + +// Run the application with the given arguments. Output is sent to t. +func (app *App) Run(t Term, args ...string) int { + help := app.commands["help"] + + if len(args) == 0 || strings.HasPrefix(args[0], "-") { + help.Run(t) + return 2 + } + cmd, ok := app.commands[args[0]] + if !ok { + help.Run(t) + return 2 + } + + return cmd.Run(t, args[1:]...) +} + +var tmpl = ` +usage: {{ .Name }} [] + +Available commands: + {{ range .Commands }}{{ .Name }} {{ .Desc }} + {{ end }} +` + +// BasicHelp returns a function that creates a basic help text for the application +// with its commands. +func BasicHelp(app *App, ts string) func() string { + t := template.Must(template.New("help").Parse(ts)) + + return func() string { + type command struct { + Name, Desc string + } + cmds := []command{} + + var maxLen int + names := []string{} + for name := range app.commands { + names = append(names, name) + if len(name) > maxLen { + maxLen = len(name) + } + } + sort.Strings(names) + + for _, name := range names { + cmds = append(cmds, command{ + Name: name + strings.Repeat(" ", maxLen-len(name)), + Desc: app.commands[name].Desc, + }) + } + + var buf bytes.Buffer + t.Execute(&buf, struct { + Name string + Commands []command + }{ + Name: app.Name, + Commands: cmds, + }) + return strings.TrimSpace(buf.String()) + } +}