commit aa1bf9dcb57f6b8f10693cd3ac0fddf5dbbe03fb Author: Alec Thomas Date: Tue Apr 10 16:51:06 2018 +1000 Initial commit. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a6a39b8 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# Kong is a command-line parser for Go + +It parses a command-line into a struct. eg. + +```go +package main + +import "github.com/alecthomas/kong" + +var CLI struct { + Rm struct { + Force bool `help:"Force removal."` + Recursive bool `help:"Recursively remove files."` + + Paths []string `help:"Paths to remove." type:"path"` + } `help:"Remove files."` + + Ls struct { + Paths []string `help:"Paths to list." type:"path"` + } `help:"List paths."` +} + +func main() { + kong.Parse(&CLI) +} +``` diff --git a/_examples/shell/main.go b/_examples/shell/main.go new file mode 100644 index 0000000..352cfec --- /dev/null +++ b/_examples/shell/main.go @@ -0,0 +1,20 @@ +package main + +import "github.com/alecthomas/kong" + +var CLI struct { + Rm struct { + Force bool `help:"Force removal."` + Recursive bool `help:"Recursively remove files."` + + Paths []string `help:"Paths to remove." type:"path"` + } `help:"Remove files."` + + Ls struct { + Paths []string `help:"Paths to list." type:"path"` + } `help:"List paths."` +} + +func main() { + kong.Parse(&CLI) +} diff --git a/camelcase.go b/camelcase.go new file mode 100644 index 0000000..b12d111 --- /dev/null +++ b/camelcase.go @@ -0,0 +1,90 @@ +package kong + +// NOTE: This code is from https://github.com/fatih/camelcase. MIT license. + +import ( + "unicode" + "unicode/utf8" +) + +// Split splits the camelcase word and returns a list of words. It also +// supports digits. Both lower camel case and upper camel case are supported. +// For more info please check: http://en.wikipedia.org/wiki/CamelCase +// +// Examples +// +// "" => [""] +// "lowercase" => ["lowercase"] +// "Class" => ["Class"] +// "MyClass" => ["My", "Class"] +// "MyC" => ["My", "C"] +// "HTML" => ["HTML"] +// "PDFLoader" => ["PDF", "Loader"] +// "AString" => ["A", "String"] +// "SimpleXMLParser" => ["Simple", "XML", "Parser"] +// "vimRPCPlugin" => ["vim", "RPC", "Plugin"] +// "GL11Version" => ["GL", "11", "Version"] +// "99Bottles" => ["99", "Bottles"] +// "May5" => ["May", "5"] +// "BFG9000" => ["BFG", "9000"] +// "BöseÜberraschung" => ["Böse", "Überraschung"] +// "Two spaces" => ["Two", " ", "spaces"] +// "BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"] +// +// Splitting rules +// +// 1) If string is not valid UTF-8, return it without splitting as +// single item array. +// 2) Assign all unicode characters into one of 4 sets: lower case +// letters, upper case letters, numbers, and all other characters. +// 3) Iterate through characters of string, introducing splits +// between adjacent characters that belong to different sets. +// 4) Iterate through array of split strings, and if a given string +// is upper case: +// if subsequent string is lower case: +// move last character of upper case string to beginning of +// lower case string +func camelCase(src string) (entries []string) { + // don't split invalid utf8 + if !utf8.ValidString(src) { + return []string{src} + } + entries = []string{} + var runes [][]rune + lastClass := 0 + // split into fields based on class of unicode character + for _, r := range src { + var class int + switch true { + case unicode.IsLower(r): + class = 1 + case unicode.IsUpper(r): + class = 2 + case unicode.IsDigit(r): + class = 3 + default: + class = 4 + } + if class == lastClass { + runes[len(runes)-1] = append(runes[len(runes)-1], r) + } else { + runes = append(runes, []rune{r}) + } + lastClass = class + } + // handle upper case -> lower case sequences, e.g. + // "PDFL", "oader" -> "PDF", "Loader" + for i := 0; i < len(runes)-1; i++ { + if unicode.IsUpper(runes[i][0]) && unicode.IsLower(runes[i+1][0]) { + runes[i+1] = append([]rune{runes[i][len(runes[i])-1]}, runes[i+1]...) + runes[i] = runes[i][:len(runes[i])-1] + } + } + // construct []string from results + for _, s := range runes { + if len(s) > 0 { + entries = append(entries, string(s)) + } + } + return +} diff --git a/global.go b/global.go new file mode 100644 index 0000000..2acabe9 --- /dev/null +++ b/global.go @@ -0,0 +1,10 @@ +package kong + +import "os" + +func Parse(cli interface{}) { + parser, err := New("", "", cli) + parser.FatalIfErrorf(err) + err = parser.Parse(os.Args[1:]) + parser.FatalIfErrorf(err) +} diff --git a/kong.go b/kong.go new file mode 100644 index 0000000..0edecac --- /dev/null +++ b/kong.go @@ -0,0 +1,47 @@ +package kong + +import ( + "fmt" + "os" + "path/filepath" +) + +type Kong struct { + Model *ApplicationModel + // Termination function (defaults to os.Exit) + Terminate func(int) +} + +func New(name, description string, grammar interface{}) (*Kong, error) { + if name == "" { + name = filepath.Base(os.Args[0]) + } + return &Kong{ + Model: &ApplicationModel{ + Name: name, + Description: description, + }, + Terminate: os.Exit, + }, nil +} + +// Parse arguments into target. +func (k *Kong) Parse(args []string) error { + return nil +} + +func (k *Kong) Errorf(format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, k.Model.Name+": "+format, args...) +} + +func (k *Kong) FatalIfErrorf(err error, args ...interface{}) { + if err == nil { + return + } + msg := err.Error() + if len(args) == 0 { + msg = fmt.Sprintf(args[0].(string), args...) + ": " + err.Error() + } + k.Errorf("%s", msg) + k.Terminate(1) +} diff --git a/model.go b/model.go new file mode 100644 index 0000000..d8a5647 --- /dev/null +++ b/model.go @@ -0,0 +1,44 @@ +package kong + +import "flag" + +type Value = flag.Getter + +type ApplicationModel struct { + Name string + Description string + + NodeModel +} + +type NodeModel struct { + Groups []*GroupModel + // Positional arguments. + Arguments []*ArgumentModel +} + +type GroupModel struct { + // Flags. + Flags []*FlagModel + // Command hierarchy. + Commands []*CommandModel +} + +type ValueModel struct { + Name string + Help string + Value flag.Value +} + +type CommandModel struct { + NodeModel +} + +type ArgumentModel struct { + ValueModel +} + +type FlagModel struct { + ValueModel + Short rune +}