You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
322 lines
7.7 KiB
322 lines
7.7 KiB
diff --git a/cmd/golist/golist.go b/cmd/golist/golist.go |
|
index ee028ae..e9c038d 100644 |
|
--- a/cmd/golist/golist.go |
|
+++ b/cmd/golist/golist.go |
|
@@ -11,9 +11,8 @@ import ( |
|
"strings" |
|
"text/template" |
|
|
|
- "github.com/urfave/cli" |
|
- |
|
"pagure.io/golist/pkg/util" |
|
+ "pagure.io/golist/pkg/cli" |
|
) |
|
|
|
var ( |
|
@@ -100,7 +99,6 @@ func main() { |
|
} |
|
|
|
app.Action = func(c *cli.Context) error { |
|
- |
|
if len(c.StringSlice("package-path")) == 0 { |
|
return fmt.Errorf("--package-path is not set") |
|
} |
|
diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go |
|
new file mode 100644 |
|
index 0000000..ec91056 |
|
--- /dev/null |
|
+++ b/pkg/cli/cli.go |
|
@@ -0,0 +1,293 @@ |
|
+package cli |
|
+ |
|
+/* golist uses a very small portion of functionality |
|
+ from github.com/urfave/cli. This module provides |
|
+ minimal substitute API implementations for only the |
|
+ core functionality used by golist, for the purpose of |
|
+ bootstrapping golist without additional dependencies. |
|
+*/ |
|
+ |
|
+import ( |
|
+ "strings" |
|
+ "fmt" |
|
+ "os" |
|
+) |
|
+ |
|
+type String string |
|
+type StringSlice []string |
|
+type Bool bool |
|
+ |
|
+type Flag interface { |
|
+ name() string |
|
+ usage() string |
|
+} |
|
+ |
|
+type BoolFlag struct { |
|
+ Name string |
|
+ Usage string |
|
+ Value bool |
|
+} |
|
+ |
|
+type StringFlag struct { |
|
+ Name string |
|
+ Usage string |
|
+ Value string |
|
+} |
|
+ |
|
+type StringSliceFlag struct { |
|
+ Name string |
|
+ Usage string |
|
+ Value *StringSlice |
|
+} |
|
+ |
|
+type App struct { |
|
+ Name string |
|
+ Usage string |
|
+ Version string |
|
+ Flags []Flag |
|
+ Action func(*Context) error |
|
+ |
|
+} |
|
+ |
|
+func NewApp() App { |
|
+ var a App |
|
+ return a |
|
+} |
|
+ |
|
+func (a *App) Run(osArgs []string) error { |
|
+ c, err := newContext(a.Flags, osArgs) |
|
+ if err != nil { |
|
+ return err |
|
+ } |
|
+ if c.Bool("help") { |
|
+ a.PrintHelp() |
|
+ os.Exit(0) |
|
+ } |
|
+ return a.Action(c) |
|
+} |
|
+ |
|
+func (a *App) PrintHelp() { |
|
+ maxNameLen := 0 |
|
+ for _, flag := range a.Flags { |
|
+ length := len(flag.name()) |
|
+ if length > maxNameLen { |
|
+ maxNameLen = length |
|
+ } |
|
+ } |
|
+ fmtSpec := "%-" + fmt.Sprintf("%v", maxNameLen + 6) + "s\t%s\n" |
|
+ fmt.Printf("%s - %s\n", a.Name, a.Usage) |
|
+ fmt.Printf("Options:\n") |
|
+ for _, flag := range a.Flags { |
|
+ flagNameSlice := append([]string{canonicalName(flag)}, alternateNames(flag)...) |
|
+ for i, _ := range flagNameSlice { |
|
+ if len(flagNameSlice[i]) > 1 { |
|
+ flagNameSlice[i] = fmt.Sprintf("--%s", flagNameSlice[i]) |
|
+ } else { |
|
+ flagNameSlice[i] = fmt.Sprintf("-%s", flagNameSlice[i]) |
|
+ } |
|
+ } |
|
+ flagNameStr := strings.Join(flagNameSlice, ", ") |
|
+ switch flag.(type) { |
|
+ case StringFlag: |
|
+ flagNameStr = fmt.Sprintf(" %s value", flagNameStr) |
|
+ case StringSliceFlag: |
|
+ flagNameStr = fmt.Sprintf(" %s value", flagNameStr) |
|
+ case BoolFlag: |
|
+ flagNameStr = fmt.Sprintf(" %s", flagNameStr) |
|
+ } |
|
+ fmt.Printf(fmtSpec, flagNameStr, flag.usage()) |
|
+ } |
|
+} |
|
+ |
|
+ |
|
+type Context struct { |
|
+ flagValues map[string]interface{} |
|
+ flagDecls map[string]Flag |
|
+ altFlags map[string]string |
|
+ positionalArgs []string |
|
+} |
|
+ |
|
+func (c *Context) Bool(flag string) bool { |
|
+ iface, ok := c.flagDecls[flag] |
|
+ if !ok { |
|
+ panic("undefined flag" + flag) |
|
+ } |
|
+ switch iface.(type) { |
|
+ case BoolFlag: |
|
+ break |
|
+ default: |
|
+ panic("flag type mismatch - expected BoolFlag, got: " + flag) |
|
+ } |
|
+ val, ok := c.flagValues[flag] |
|
+ if !ok { |
|
+ return iface.(BoolFlag).Value |
|
+ } |
|
+ return val.(bool) |
|
+} |
|
+ |
|
+func (c *Context) String(flag string) string { |
|
+ iface, ok := c.flagDecls[flag] |
|
+ if !ok { |
|
+ panic("undefined flag" + flag) |
|
+ } |
|
+ switch iface.(type) { |
|
+ case StringFlag: |
|
+ break |
|
+ default: |
|
+ panic("flag type mismatch - expected StringFlag, got: " + flag) |
|
+ } |
|
+ val, ok:= c.flagValues[flag] |
|
+ if !ok { |
|
+ return iface.(StringFlag).Value |
|
+ } |
|
+ return val.(string) |
|
+} |
|
+ |
|
+func (c *Context) StringSlice(flag string) []string { |
|
+ iface, ok := c.flagDecls[flag]; |
|
+ if !ok { |
|
+ panic("undefined flag" + flag) |
|
+ } |
|
+ switch iface.(type) { |
|
+ case StringSliceFlag: |
|
+ break |
|
+ default: |
|
+ panic("flag type mismatch - expected StringSliceFlag, got: " + flag) |
|
+ } |
|
+ val, ok := c.flagValues[flag] |
|
+ if !ok { |
|
+ val = iface.(StringSliceFlag).Value |
|
+ if val != nil { |
|
+ return []string{} |
|
+ } |
|
+ } |
|
+ return val.([]string) |
|
+} |
|
+ |
|
+// Create a hash mapping from flag names to declarations |
|
+// and alt names to flag names. |
|
+func (c *Context) setupFlags(flagDecls []Flag) error { |
|
+ helpFlag := BoolFlag { |
|
+ Name: "help, h", |
|
+ Usage: "Show help message", |
|
+ } |
|
+ flagDecls = append(flagDecls, helpFlag) |
|
+ for _, flag := range flagDecls { |
|
+ flagName := canonicalName(flag) |
|
+ if _, ok:= c.flagDecls[flagName]; ok { |
|
+ return fmt.Errorf("cannot redeclare flag: %s", flagName) |
|
+ } |
|
+ c.flagDecls[flagName] = flag |
|
+ altFlagNames := alternateNames(flag) |
|
+ for _, altName := range altFlagNames { |
|
+ c.altFlags[altName] = flagName |
|
+ } |
|
+ } |
|
+ return nil |
|
+} |
|
+ |
|
+func (c *Context) parseArgs(osArgs []string) error { |
|
+ // process command line arguments as a stream of tokens. |
|
+ // operations consume the first token in the stream until |
|
+ // the stream is empty. |
|
+ argStream := osArgs |
|
+ for len(argStream) > 0 { |
|
+ arg := argStream[0] |
|
+ if ! isFlag(arg) { |
|
+ argStream = argStream[1:] |
|
+ c.positionalArgs = append(c.positionalArgs, arg) |
|
+ |
|
+ } else { |
|
+ arg = trimFlag(arg) |
|
+ if _, ok:= c.altFlags[arg]; ok { |
|
+ arg = c.altFlags[arg] |
|
+ } |
|
+ iface, ok := c.flagDecls[arg] |
|
+ if !ok { |
|
+ return fmt.Errorf("unexpected argument: %v", arg) |
|
+ } |
|
+ switch flag := iface.(type) { |
|
+ case StringFlag: |
|
+ argStream = argStream[1:] |
|
+ if len(argStream) == 0 { |
|
+ return fmt.Errorf("expected value for argument: %v", arg) |
|
+ } |
|
+ if isFlag(argStream[0]) { |
|
+ return fmt.Errorf("unexpected flag: %v", arg) |
|
+ } |
|
+ c.flagValues[arg] = argStream[0] |
|
+ case StringSliceFlag: |
|
+ argStream = argStream[1:] |
|
+ if len (argStream) == 0 { |
|
+ return fmt.Errorf("expected value for argument: %v", arg) |
|
+ } |
|
+ if isFlag(argStream[0]) { |
|
+ return fmt.Errorf("unexpected flag: %v", arg) |
|
+ } |
|
+ c.flagValues[arg] = make([]string, 0) |
|
+ c.flagValues[arg] = append(c.flagValues[arg].([]string), argStream[0]) |
|
+ argStream = argStream[1:] |
|
+ for len(argStream) > 0 && ! isFlag(argStream[0]) { |
|
+ c.flagValues[arg] = append(c.flagValues[arg].([]string), argStream[0]) |
|
+ argStream = argStream[1:] |
|
+ } |
|
+ case BoolFlag: |
|
+ argStream = argStream[1:] |
|
+ c.flagValues[canonicalName(flag)] = true |
|
+ default: |
|
+ return fmt.Errorf("unexpected flag: %v", arg) |
|
+ } |
|
+ } |
|
+ } |
|
+ return nil |
|
+} |
|
+ |
|
+func newContext(flags []Flag, osArgs []string) (*Context, error) { |
|
+ var c Context |
|
+ c.flagValues = make(map[string]interface{}) |
|
+ c.flagDecls = make(map[string]Flag) |
|
+ c.altFlags = make(map[string]string) |
|
+ c.altFlags = make(map[string]string) |
|
+ if err := c.setupFlags(flags); err != nil { |
|
+ return nil, err |
|
+ } |
|
+ if err := c.parseArgs(osArgs); err != nil { |
|
+ return nil, err |
|
+ } |
|
+ return &c, nil |
|
+} |
|
+ |
|
+func (f StringFlag) name() string {return f.Name} |
|
+func (f StringFlag) usage() string {return f.Usage} |
|
+func (f BoolFlag) name() string {return f.Name} |
|
+func (f BoolFlag) usage() string {return f.Usage} |
|
+func (f StringSliceFlag) name() string {return f.Name} |
|
+func (f StringSliceFlag) usage() string {return f.Usage} |
|
+ |
|
+// takes a Flag with a comma delimited string |
|
+// of flag names and returns the first one |
|
+func canonicalName(flag Flag) string { |
|
+ flagNames := strings.Split(flag.name(), ",") |
|
+ return strings.TrimSpace(flagNames[0]) |
|
+} |
|
+ |
|
+// takes a Flag with a comma delimited string |
|
+// of flag names and returns them as a string slice |
|
+// with the canonical (first) flag ommitted |
|
+func alternateNames(flag Flag) []string { |
|
+ flagNames := strings.Split(flag.name(), ",") |
|
+ altNames := flagNames[1:] |
|
+ for i, _ := range altNames { |
|
+ altNames[i] = strings.TrimSpace(altNames[i]) |
|
+ } |
|
+ return altNames |
|
+} |
|
+ |
|
+func isFlag(arg string) bool { |
|
+ return strings.HasPrefix(arg, "-") |
|
+} |
|
+ |
|
+func trimFlag(arg string) string { |
|
+ return strings.Trim(strings.Trim(arg, "-"), "-") |
|
+} |
|
+
|
|
|