├── .travis.yml ├── cli ├── environment.go ├── command.go ├── doc.go ├── register.go ├── manual.go ├── manual_man.go └── execute.go ├── LICENSE └── example └── main.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 1.1 3 | -------------------------------------------------------------------------------- /cli/environment.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type environment_t struct { 8 | args []string 9 | env map[string]string 10 | } 11 | 12 | func new_environment(args []string, env []string) *environment_t { 13 | return &environment_t{args: args, env: parse_env(env)} 14 | } 15 | 16 | func parse_env(in []string) map[string]string { 17 | m := make(map[string]string, len(in)) 18 | 19 | for _, l := range in { 20 | parts := strings.SplitN(l, "=", 2) 21 | if len(parts) == 1 { 22 | m[parts[0]] = "" 23 | } else { 24 | m[parts[0]] = parts[1] 25 | } 26 | } 27 | 28 | return m 29 | } 30 | -------------------------------------------------------------------------------- /cli/command.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | /* 8 | 9 | Example command: 10 | type Cmd struct { 11 | Root|ParentCommand 12 | Arg0 13 | 14 | FlagsAndEnvs 15 | Args 16 | } 17 | 18 | */ 19 | 20 | type Command interface { 21 | Main() error 22 | } 23 | 24 | type Arg0 string 25 | 26 | type Root struct { 27 | Help bool `flag:"-h,--help"` 28 | 29 | Manual ` 30 | 31 | .Help: 32 | Show the help message for the current command. 33 | 34 | ` 35 | } 36 | 37 | type Manual struct { 38 | exec *executable_t 39 | usage string 40 | summary string 41 | options map[string]section_t 42 | sections []section_t 43 | } 44 | 45 | type section_t struct { 46 | Header string 47 | Body string 48 | } 49 | 50 | var ( 51 | typ_Command = reflect.TypeOf((*Command)(nil)).Elem() 52 | typ_Arg0 = reflect.TypeOf((*Arg0)(nil)).Elem() 53 | typ_Root = reflect.TypeOf((*Root)(nil)).Elem() 54 | typ_Manual = reflect.TypeOf((*Manual)(nil)).Elem() 55 | ) 56 | -------------------------------------------------------------------------------- /cli/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Package cli provides an advanced CLI framework. 4 | 5 | Commands are defined as structs wich must be registerd with Register(T). 6 | 7 | type Command struct { 8 | // The parent command must always be present and the first field 9 | // It defaults to cli.Root ban it can be any command type. 10 | cli.Root 11 | 12 | // The name of the command as it was passed to exec() 13 | cli.Arg0 14 | 15 | // flag: env: and arg can be combined 16 | Field Type `flag:"-f,--flag"` 17 | Field Type `env:"VAR"` 18 | Field Type `arg` 19 | 20 | // The manual for this command 21 | cli.Manual ` 22 | Usage: a usage example 23 | Summary: a short decription of the command 24 | 25 | .Field: 26 | A description for the field Field. 27 | 28 | Section: 29 | A manual section and header. 30 | ` 31 | } 32 | 33 | func init() { 34 | // register the command. (must happen during init()) 35 | cli.Register(Command{}) 36 | } 37 | 38 | */ 39 | package cli 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 Simon Menke 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/fd/go-cli/cli" 6 | "os" 7 | ) 8 | 9 | func init() { 10 | cli.Register(Root{}) 11 | cli.Register(App{}) 12 | cli.Register(AppList{}) 13 | cli.Register(AppCreate{}) 14 | cli.Register(AppDestroy{}) 15 | } 16 | 17 | type Root struct { 18 | cli.Root 19 | cli.Arg0 20 | 21 | Verbose bool `flag:"-v,--verbose" env:"VERBOSE"` 22 | Debug bool `flag:"--debug" env:"DEBUG"` 23 | 24 | cli.Manual ` 25 | Usage: example CMD ...ARGS 26 | Summary: Example cli tool. 27 | 28 | .Verbose: 29 | Show more details output. 30 | ` 31 | } 32 | 33 | type App struct { 34 | Root 35 | cli.Arg0 `name:"application"` 36 | 37 | cli.Manual ` 38 | Usage: example app CMD ...ARGS 39 | Summary: Manage the applications. 40 | ` 41 | } 42 | 43 | type AppList struct { 44 | App 45 | cli.Arg0 `name:"list"` 46 | 47 | cli.Manual ` 48 | Usage: example app list 49 | Summary: List the applications. 50 | ` 51 | } 52 | 53 | type AppCreate struct { 54 | App 55 | cli.Arg0 `name:"create"` 56 | Region string `flag:"--region" env:"REGION"` 57 | Name string `arg` 58 | 59 | cli.Manual ` 60 | Usage: example app create NAME 61 | Summary: Create an application. 62 | .Region: The region to create the app in 63 | .Name: The *name* of the application 64 | ` 65 | } 66 | 67 | type AppDestroy struct { 68 | App 69 | cli.Arg0 `name:"destroy"` 70 | Name string `arg` 71 | 72 | cli.Manual ` 73 | Usage: example app destroy NAME 74 | Summary: Destroy an application. 75 | .Name: The *name* of the application 76 | ` 77 | } 78 | 79 | func (cmd *AppList) Main() error { 80 | if cmd.Help { 81 | fmt.Printf("CMD: %+v\n", cmd) 82 | } 83 | return nil 84 | } 85 | 86 | func (cmd *AppCreate) Main() error { 87 | if cmd.Help { 88 | cmd.Manual.Open() 89 | } 90 | return nil 91 | } 92 | 93 | func (cmd *AppDestroy) Main() error { 94 | return nil 95 | } 96 | 97 | func main() { 98 | cli.Main(os.Args, os.Environ()) 99 | } 100 | -------------------------------------------------------------------------------- /cli/register.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | // "fmt" 5 | "reflect" 6 | "strings" 7 | ) 8 | 9 | var executables = map[reflect.Type]*executable_t{} 10 | 11 | func init() { 12 | Register(Root{}) 13 | } 14 | 15 | type executable_t struct { 16 | Type reflect.Type 17 | IsGroup bool 18 | Names []string 19 | ParentExec *executable_t 20 | Parent reflect.StructField 21 | Arg0 reflect.StructField 22 | manual reflect.StructField 23 | parsed_manual *Manual 24 | Variables []reflect.StructField 25 | Flags []reflect.StructField 26 | Args []reflect.StructField 27 | SubCommands []*executable_t 28 | } 29 | 30 | func Register(v interface{}) { 31 | RegisterType(reflect.TypeOf(v)) 32 | } 33 | 34 | func RegisterType(t reflect.Type) { 35 | if _, p := executables[t]; p { 36 | return 37 | } 38 | 39 | var ( 40 | exec = &executable_t{} 41 | ) 42 | executables[t] = exec 43 | exec.Type = t 44 | 45 | if t.Kind() != reflect.Struct { 46 | panic("command must be a struct") 47 | } 48 | 49 | c := -1 50 | for i, l := 0, t.NumField(); i < l; i++ { 51 | f := t.Field(i) 52 | if f.PkgPath != "" { 53 | continue 54 | } 55 | c++ 56 | f.Index = []int{i} 57 | 58 | // first public field 59 | if c == 0 && t != typ_Root { 60 | exec.Parent = f 61 | continue 62 | } 63 | 64 | if f.Type == typ_Arg0 { 65 | if tag := f.Tag.Get("name"); tag != "" { 66 | exec.Names = strings.Split(tag, ",") 67 | } 68 | exec.Arg0 = f 69 | continue 70 | } 71 | 72 | if f.Type == typ_Manual { 73 | exec.manual = f 74 | continue 75 | } 76 | 77 | if f.Tag.Get("env") != "" { 78 | exec.Variables = append(exec.Variables, f) 79 | } 80 | 81 | if f.Tag.Get("flag") != "" { 82 | exec.Flags = append(exec.Flags, f) 83 | } 84 | 85 | if strings.Contains(string(f.Tag), "arg") { 86 | exec.Args = append(exec.Args, f) 87 | } 88 | } 89 | 90 | if !reflect.PtrTo(t).Implements(typ_Command) { 91 | exec.IsGroup = true 92 | } 93 | 94 | if exec.Parent.Type != nil { 95 | RegisterType(exec.Parent.Type) 96 | parent_exec := executables[exec.Parent.Type] 97 | if !parent_exec.IsGroup { 98 | panic("the parent command must be a group") 99 | } 100 | exec.ParentExec = parent_exec 101 | parent_exec.SubCommands = append(parent_exec.SubCommands, exec) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /cli/manual.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "unicode" 7 | ) 8 | 9 | func (exec *executable_t) Manual() *Manual { 10 | if exec.manual.Type == nil { 11 | return nil 12 | } 13 | 14 | if exec.parsed_manual != nil { 15 | return exec.parsed_manual 16 | } 17 | 18 | m := &Manual{} 19 | m.parse(exec) 20 | exec.parsed_manual = m 21 | 22 | return m 23 | } 24 | 25 | func (m *Manual) parse(exec *executable_t) { 26 | var ( 27 | source = string(exec.manual.Tag) 28 | indent = determine_indent(source) 29 | lines = strings.Split(source, "\n") 30 | ) 31 | 32 | m.exec = exec 33 | m.options = make(map[string]section_t, 20) 34 | 35 | if p := exec.ParentExec; p != nil { 36 | if pm := p.Manual(); pm != nil { 37 | for k, o := range pm.options { 38 | m.options[k] = o 39 | } 40 | } 41 | } 42 | 43 | remove_indent(lines, indent) 44 | m.parse_sections(lines) 45 | } 46 | 47 | func (m *Manual) parse_sections(lines []string) { 48 | var ( 49 | in_section bool 50 | section_name string 51 | section_body string 52 | ) 53 | 54 | for _, line := range lines { 55 | name, body, empty := parse_line(line) 56 | 57 | if empty { 58 | if in_section { 59 | section_body += "\n" 60 | } 61 | continue 62 | } 63 | 64 | if name != "" { 65 | if in_section { 66 | m.parse_section(section_name, section_body) 67 | section_name = "" 68 | section_body = "" 69 | } 70 | 71 | in_section = true 72 | section_name = name 73 | } 74 | 75 | section_body += body + "\n" 76 | } 77 | 78 | if in_section { 79 | m.parse_section(section_name, section_body) 80 | } 81 | } 82 | 83 | func (m *Manual) parse_section(name, body string) { 84 | { 85 | var ( 86 | indent = determine_indent(body) 87 | lines = strings.Split(body, "\n") 88 | ) 89 | 90 | remove_indent(lines, indent) 91 | body = strings.TrimSpace(strings.Join(lines, "\n")) 92 | } 93 | 94 | switch name { 95 | 96 | case "Usage": 97 | m.usage = body 98 | 99 | case "Summary": 100 | m.summary = body 101 | 102 | default: 103 | 104 | if strings.HasPrefix(name, ".") { 105 | p := section_t{Header: name, Body: body} 106 | m.options[name[1:]] = p 107 | 108 | } else { 109 | p := section_t{Header: name, Body: body} 110 | m.sections = append(m.sections, p) 111 | } 112 | 113 | } 114 | } 115 | 116 | func parse_line(line string) (section, body string, empty bool) { 117 | if len(line) == 0 || is_space_only(line) { 118 | return "", "", true 119 | } 120 | 121 | if unicode.IsSpace([]rune(line)[0]) { 122 | return "", line, false 123 | } 124 | 125 | parts := strings.SplitN(line, ":", 2) 126 | if len(parts) != 2 { 127 | panic(fmt.Sprintf("Invalid indentation for line: `%s`", line)) 128 | } 129 | 130 | return strings.TrimSpace(parts[0]), strings.TrimLeft(parts[1], " \t"), false 131 | } 132 | 133 | func remove_indent(lines []string, n int) { 134 | for i, line := range lines { 135 | lines[i] = skip_at_most_n_spaces(line, n) 136 | } 137 | } 138 | 139 | func determine_indent(source string) int { 140 | var ( 141 | indent int 142 | ) 143 | 144 | for _, c := range source { 145 | if c == '\n' { 146 | indent = 0 147 | } else if unicode.IsSpace(c) { 148 | indent += 1 149 | } else { 150 | break 151 | } 152 | } 153 | 154 | return indent 155 | } 156 | 157 | func skip_at_most_n_spaces(line string, n int) string { 158 | var ( 159 | prefix string 160 | suffix string 161 | ) 162 | 163 | if len(line) < n { 164 | prefix = line 165 | } else { 166 | prefix = line[:n] 167 | suffix = line[n:] 168 | } 169 | 170 | if is_space_only(prefix) { 171 | return suffix 172 | } 173 | 174 | panic(fmt.Sprintf("Invalid indentation for line: `%s`", line)) 175 | } 176 | 177 | func is_space_only(s string) bool { 178 | for _, c := range s { 179 | if !unicode.IsSpace(c) { 180 | return false 181 | } 182 | } 183 | return true 184 | } 185 | -------------------------------------------------------------------------------- /cli/manual_man.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "github.com/knieriem/markdown" 8 | "io" 9 | "os" 10 | "os/exec" 11 | "sort" 12 | "strconv" 13 | "strings" 14 | ) 15 | 16 | var ( 17 | mmparser = markdown.NewParser(nil) 18 | ) 19 | 20 | func (m *Manual) Open() { 21 | r, w, err := os.Pipe() 22 | if err != nil { 23 | panic(err) 24 | } 25 | 26 | cmd := exec.Command("groffer", "--tty") 27 | cmd.Stdin = r 28 | cmd.Stdout = os.Stdout 29 | cmd.Stderr = os.Stderr 30 | err = cmd.Start() 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | m.format_man(w) 36 | w.Close() 37 | r.Close() 38 | 39 | err = cmd.Wait() 40 | if err != nil { 41 | panic(err) 42 | } 43 | } 44 | 45 | func (m *Manual) format_man(w io.Writer) { 46 | if w == nil { 47 | w = os.Stdout 48 | } 49 | 50 | buf := bufio.NewWriter(w) 51 | defer buf.Flush() 52 | 53 | fmt.Fprintf(buf, ".TH command 1\n") 54 | m.name(buf) 55 | m.synopsis(buf) 56 | m.flags(buf) 57 | m.args(buf) 58 | m.vars(buf) 59 | m.commands(buf) 60 | 61 | for _, s := range m.sections { 62 | s.Man(buf) 63 | } 64 | } 65 | 66 | func (m *Manual) name(w *bufio.Writer) { 67 | s := section_t{ 68 | Header: "NAME", 69 | Body: m.summary, 70 | } 71 | s.Man(w) 72 | } 73 | 74 | func (m *Manual) synopsis(w *bufio.Writer) { 75 | s := section_t{ 76 | Header: "SYNOPSIS", 77 | Body: m.usage, 78 | } 79 | s.Man(w) 80 | } 81 | 82 | func (m *Manual) flags(w *bufio.Writer) { 83 | var ( 84 | buf bytes.Buffer 85 | parts []string 86 | exec = m.exec 87 | ) 88 | 89 | mmf := markdown.ToGroffMM(&buf) 90 | 91 | for exec != nil { 92 | for _, f := range exec.Flags { 93 | s, p := m.options[f.Name] 94 | if !p { 95 | continue 96 | } 97 | 98 | buf.Reset() 99 | mmparser.Markdown(bytes.NewReader([]byte(s.Body)), mmf) 100 | body := buf.String() 101 | body = strings.TrimPrefix(body, ".P\n") 102 | 103 | part := fmt.Sprintf(".TP\n\\fB%s\\fP\n%s", f.Tag.Get("flag"), body) 104 | parts = append(parts, part) 105 | } 106 | exec = exec.ParentExec 107 | } 108 | 109 | if len(parts) > 0 { 110 | fmt.Fprintf(w, ".SH OPTIONS\n") 111 | sort.Strings(parts) 112 | for _, part := range parts { 113 | fmt.Fprint(w, part) 114 | } 115 | fmt.Fprintf(w, ".P\n") 116 | } 117 | } 118 | 119 | func (m *Manual) vars(w *bufio.Writer) { 120 | var ( 121 | buf bytes.Buffer 122 | parts []string 123 | exec = m.exec 124 | ) 125 | 126 | mmf := markdown.ToGroffMM(&buf) 127 | 128 | for exec != nil { 129 | for _, f := range exec.Variables { 130 | s, p := m.options[f.Name] 131 | if !p { 132 | continue 133 | } 134 | 135 | if flag := f.Tag.Get("flag"); flag != "" { 136 | s.Body = fmt.Sprintf("See %s", flag) 137 | } 138 | 139 | buf.Reset() 140 | mmparser.Markdown(bytes.NewReader([]byte(s.Body)), mmf) 141 | body := buf.String() 142 | body = strings.TrimPrefix(body, ".P\n") 143 | 144 | part := fmt.Sprintf(".TP\n\\fB%s\\fP\n%s", f.Tag.Get("env"), body) 145 | parts = append(parts, part) 146 | } 147 | exec = exec.ParentExec 148 | } 149 | 150 | if len(parts) > 0 { 151 | fmt.Fprintf(w, ".SH \"ENVIRONMENT VARIABLES\"\n") 152 | sort.Strings(parts) 153 | for _, part := range parts { 154 | fmt.Fprint(w, part) 155 | } 156 | fmt.Fprintf(w, ".P\n") 157 | } 158 | } 159 | 160 | func (m *Manual) args(w *bufio.Writer) { 161 | var ( 162 | buf bytes.Buffer 163 | parts []string 164 | ) 165 | 166 | mmf := markdown.ToGroffMM(&buf) 167 | 168 | for _, f := range m.exec.Args { 169 | s, p := m.options[f.Name] 170 | if !p { 171 | continue 172 | } 173 | 174 | buf.Reset() 175 | mmparser.Markdown(bytes.NewReader([]byte(s.Body)), mmf) 176 | body := buf.String() 177 | body = strings.TrimPrefix(body, ".P\n") 178 | 179 | part := fmt.Sprintf(".TP\n\\fB%s\\fP\n%s", f.Name, body) 180 | parts = append(parts, part) 181 | } 182 | 183 | if len(parts) > 0 { 184 | fmt.Fprintf(w, ".SH ARGUMENTS\n") 185 | sort.Strings(parts) 186 | for _, part := range parts { 187 | fmt.Fprint(w, part) 188 | } 189 | fmt.Fprintf(w, ".P\n") 190 | } 191 | } 192 | 193 | func (m *Manual) commands(w *bufio.Writer) { 194 | var ( 195 | buf bytes.Buffer 196 | parts []string 197 | ) 198 | 199 | mmf := markdown.ToGroffMM(&buf) 200 | 201 | for _, subcmd := range m.exec.SubCommands { 202 | subm := subcmd.Manual() 203 | if subm == nil || subm.summary == "" { 204 | continue 205 | } 206 | 207 | buf.Reset() 208 | mmparser.Markdown(bytes.NewReader([]byte(subm.summary)), mmf) 209 | body := buf.String() 210 | body = strings.TrimPrefix(body, ".P\n") 211 | 212 | part := fmt.Sprintf(".TP\n\\fB%s\\fP\n%s", strings.Join(subcmd.Names, ", "), body) 213 | parts = append(parts, part) 214 | } 215 | 216 | if len(parts) > 0 { 217 | fmt.Fprintf(w, ".SH COMMANDS\n") 218 | sort.Strings(parts) 219 | for _, part := range parts { 220 | fmt.Fprint(w, part) 221 | } 222 | fmt.Fprintf(w, ".P\n") 223 | } 224 | } 225 | 226 | func (s *section_t) Man(w *bufio.Writer) { 227 | fmt.Fprintf(w, ".SH %s\n", strconv.Quote(s.Header)) 228 | 229 | f := markdown.ToGroffMM(w) 230 | mmparser.Markdown(bytes.NewReader([]byte(s.Body)), f) 231 | fmt.Fprintf(w, ".P\n") 232 | } 233 | -------------------------------------------------------------------------------- /cli/execute.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "reflect" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | type context_t int 12 | 13 | const ( 14 | ctx_env context_t = 1 + iota 15 | ctx_arg 16 | ) 17 | 18 | func Main(args []string, environ []string) { 19 | if args == nil { 20 | args = os.Args 21 | } 22 | 23 | if environ == nil { 24 | environ = os.Environ() 25 | } 26 | 27 | env := new_environment(args, environ) 28 | exec := executables[typ_Root] 29 | err := execute(env, exec, reflect.Value{}) 30 | if err != nil { 31 | fmt.Printf("error: %s\n", err) 32 | } 33 | } 34 | 35 | func execute(env *environment_t, exec *executable_t, parent reflect.Value) error { 36 | var ( 37 | pv = reflect.New(exec.Type) 38 | v = reflect.Indirect(pv) 39 | fv reflect.Value 40 | err error 41 | ) 42 | 43 | if parent.IsValid() { 44 | fv = v.Field(exec.Parent.Index[0]) 45 | fv.Set(parent) 46 | 47 | if len(env.args) == 0 { 48 | return fmt.Errorf("expected a command") 49 | } 50 | arg := env.args[0] 51 | if exec.Arg0.Type != nil { 52 | fv = v.Field(exec.Arg0.Index[0]) 53 | fv.SetString(arg) 54 | } 55 | env.args = env.args[1:] 56 | } 57 | 58 | for _, f := range exec.Variables { 59 | fv = v.Field(f.Index[0]) 60 | err = handle_env(env, f, fv) 61 | if err != nil { 62 | return err 63 | } 64 | } 65 | 66 | for _, f := range exec.Flags { 67 | fv = v.Field(f.Index[0]) 68 | err = handle_flag(env, f, fv) 69 | if err != nil { 70 | return err 71 | } 72 | } 73 | 74 | if m := exec.Manual(); m != nil { 75 | f := exec.manual 76 | fv = v.Field(f.Index[0]) 77 | fv.Set(reflect.ValueOf(*m)) 78 | } 79 | 80 | if exec.IsGroup { 81 | return execute_group(env, exec, pv) 82 | } else { 83 | return execute_command(env, exec, pv) 84 | } 85 | 86 | panic("not reached") 87 | } 88 | 89 | func execute_command(env *environment_t, exec *executable_t, pv reflect.Value) error { 90 | var ( 91 | v = reflect.Indirect(pv) 92 | fv reflect.Value 93 | err error 94 | ) 95 | 96 | for _, f := range exec.Args { 97 | fv = v.Field(f.Index[0]) 98 | err = handle_arg(env, f, fv) 99 | if err != nil { 100 | return err 101 | } 102 | } 103 | 104 | if len(env.args) != 0 { 105 | return fmt.Errorf("unexpected arguments: %s", strings.Join(env.args, " ")) 106 | } 107 | 108 | if hv := v.FieldByName("Help"); hv.Bool() { 109 | exec.Manual().Open() 110 | return nil 111 | } 112 | 113 | return pv.Interface().(Command).Main() 114 | } 115 | 116 | func execute_group(env *environment_t, exec *executable_t, pv reflect.Value) error { 117 | var ( 118 | v = reflect.Indirect(pv) 119 | arg string 120 | ) 121 | 122 | if len(env.args) == 0 { 123 | if hv := v.FieldByName("Help"); hv.Bool() { 124 | exec.Manual().Open() 125 | return nil 126 | } 127 | return fmt.Errorf("expected a command") 128 | } 129 | arg = env.args[0] 130 | 131 | for _, sub_exec := range exec.SubCommands { 132 | if len(sub_exec.Names) > 0 { 133 | found := false 134 | for _, n := range sub_exec.Names { 135 | if n == arg { 136 | found = true 137 | break 138 | } 139 | } 140 | 141 | if !found { 142 | continue 143 | } 144 | } 145 | 146 | return execute(env, sub_exec, v) 147 | } 148 | 149 | return fmt.Errorf("unexpected arguments: %s", strings.Join(env.args, " ")) 150 | } 151 | 152 | func handle_env(env *environment_t, f reflect.StructField, fv reflect.Value) error { 153 | names := strings.Split(f.Tag.Get("env"), ",") 154 | 155 | for _, n := range names { 156 | val, p := env.env[n] 157 | if p { 158 | _, err := set_value_with_args(fv, []string{val}, ctx_env) 159 | return err 160 | } 161 | } 162 | 163 | return nil 164 | } 165 | 166 | func handle_flag(env *environment_t, f reflect.StructField, fv reflect.Value) error { 167 | var ( 168 | names = strings.Split(f.Tag.Get("flag"), ",") 169 | args = env.args 170 | skipped_args []string 171 | ) 172 | 173 | for _, n := range names { 174 | for i, l := 0, len(args); i < l; i++ { 175 | arg := args[i] 176 | 177 | if arg != n { 178 | skipped_args = append(skipped_args, arg) 179 | continue 180 | } 181 | 182 | n, err := set_value_with_args(fv, args[i+1:], ctx_arg) 183 | if err != nil { 184 | return err 185 | } 186 | 187 | i += n 188 | } 189 | 190 | args = skipped_args 191 | skipped_args = nil 192 | } 193 | 194 | env.args = args 195 | return nil 196 | } 197 | 198 | func handle_arg(env *environment_t, f reflect.StructField, fv reflect.Value) error { 199 | var ( 200 | args = env.args 201 | ) 202 | 203 | n, err := set_value_with_args(fv, args, ctx_arg) 204 | if err != nil { 205 | return err 206 | } 207 | 208 | env.args = args[n:] 209 | return nil 210 | } 211 | 212 | func set_value_with_args(fv reflect.Value, args []string, ctx context_t) (int, error) { 213 | switch fv.Kind() { 214 | 215 | case reflect.Bool: 216 | if ctx == ctx_arg { 217 | fv.SetBool(true) 218 | return 0, nil 219 | } 220 | if args[0] == "t" || args[0] == "true" || args[0] == "yes" || args[0] == "y" { 221 | fv.SetBool(true) 222 | return 1, nil 223 | } 224 | return 0, nil 225 | 226 | case reflect.String: 227 | if len(args) > 0 { 228 | fv.SetString(args[0]) 229 | return 1, nil 230 | } 231 | return 0, fmt.Errorf("expected an argument") 232 | 233 | case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64: 234 | if len(args) > 0 { 235 | i, err := strconv.ParseInt(args[0], 10, fv.Type().Bits()) 236 | if err != nil { 237 | return 0, err 238 | } 239 | fv.SetInt(i) 240 | return 1, nil 241 | } 242 | return 0, fmt.Errorf("expected an argument") 243 | 244 | case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64: 245 | if len(args) > 0 { 246 | i, err := strconv.ParseUint(args[0], 10, fv.Type().Bits()) 247 | if err != nil { 248 | return 0, err 249 | } 250 | fv.SetUint(i) 251 | return 1, nil 252 | } 253 | return 0, fmt.Errorf("expected an argument") 254 | 255 | case reflect.Float32, reflect.Float64: 256 | if len(args) > 0 { 257 | i, err := strconv.ParseFloat(args[0], fv.Type().Bits()) 258 | if err != nil { 259 | return 0, err 260 | } 261 | fv.SetFloat(i) 262 | return 1, nil 263 | } 264 | return 0, fmt.Errorf("expected an argument") 265 | 266 | } 267 | 268 | panic("Unsupported type: " + fv.Type().String()) 269 | } 270 | --------------------------------------------------------------------------------