2017-02-07 21:33:23 +00:00
package flaeg
import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"reflect"
"sort"
"strings"
"text/tabwriter"
"text/template"
2017-03-27 21:15:48 +00:00
2018-02-26 21:14:03 +00:00
"github.com/containous/flaeg/parse"
2017-03-27 21:15:48 +00:00
flag "github.com/ogier/pflag"
2017-02-07 21:33:23 +00:00
)
// ErrParserNotFound is thrown when a field is flaged but not parser match its type
2018-02-26 21:14:03 +00:00
var ErrParserNotFound = errors . New ( "parser not found or custom parser missing" )
2017-02-07 21:33:23 +00:00
2018-02-26 21:14:03 +00:00
// GetTypesRecursive links in flagMap a flag with its reflect.StructField
2017-02-07 21:33:23 +00:00
// You can whether provide objValue on a structure or a pointer to structure as first argument
2018-02-26 21:14:03 +00:00
// Flags are generated from field name or from StructTag
func getTypesRecursive ( objValue reflect . Value , flagMap map [ string ] reflect . StructField , key string ) error {
2017-02-07 21:33:23 +00:00
name := key
switch objValue . Kind ( ) {
case reflect . Struct :
for i := 0 ; i < objValue . NumField ( ) ; i ++ {
if objValue . Type ( ) . Field ( i ) . Anonymous {
2018-02-26 21:14:03 +00:00
if err := getTypesRecursive ( objValue . Field ( i ) , flagMap , name ) ; err != nil {
2017-02-07 21:33:23 +00:00
return err
}
} else if len ( objValue . Type ( ) . Field ( i ) . Tag . Get ( "description" ) ) > 0 {
fieldName := objValue . Type ( ) . Field ( i ) . Name
if ! isExported ( fieldName ) {
2018-02-26 21:14:03 +00:00
return fmt . Errorf ( "field %s is an unexported field" , fieldName )
2017-02-07 21:33:23 +00:00
}
if tag := objValue . Type ( ) . Field ( i ) . Tag . Get ( "long" ) ; len ( tag ) > 0 {
fieldName = tag
}
2018-02-26 21:14:03 +00:00
2017-02-07 21:33:23 +00:00
if len ( key ) == 0 {
name = strings . ToLower ( fieldName )
} else {
name = key + "." + strings . ToLower ( fieldName )
}
2018-02-26 21:14:03 +00:00
if _ , ok := flagMap [ name ] ; ok {
return fmt . Errorf ( "tag already exists: %s" , name )
2017-02-07 21:33:23 +00:00
}
2018-02-26 21:14:03 +00:00
flagMap [ name ] = objValue . Type ( ) . Field ( i )
2017-02-07 21:33:23 +00:00
2018-02-26 21:14:03 +00:00
if err := getTypesRecursive ( objValue . Field ( i ) , flagMap , name ) ; err != nil {
2017-02-07 21:33:23 +00:00
return err
}
}
}
case reflect . Ptr :
if len ( key ) > 0 {
2018-02-26 21:14:03 +00:00
field := flagMap [ name ]
2017-02-07 21:33:23 +00:00
field . Type = reflect . TypeOf ( false )
2018-02-26 21:14:03 +00:00
flagMap [ name ] = field
2017-02-07 21:33:23 +00:00
}
2018-02-26 21:14:03 +00:00
2017-02-07 21:33:23 +00:00
typ := objValue . Type ( ) . Elem ( )
inst := reflect . New ( typ ) . Elem ( )
2018-02-26 21:14:03 +00:00
if err := getTypesRecursive ( inst , flagMap , name ) ; err != nil {
2017-02-07 21:33:23 +00:00
return err
}
default :
return nil
}
return nil
}
2018-02-26 21:14:03 +00:00
// GetBoolFlags returns flags on pointers
2017-02-07 21:33:23 +00:00
func GetBoolFlags ( config interface { } ) ( [ ] string , error ) {
2018-02-26 21:14:03 +00:00
flagMap := make ( map [ string ] reflect . StructField )
if err := getTypesRecursive ( reflect . ValueOf ( config ) , flagMap , "" ) ; err != nil {
2017-02-07 21:33:23 +00:00
return [ ] string { } , err
}
2018-02-26 21:14:03 +00:00
flags := make ( [ ] string , 0 , len ( flagMap ) )
for f , structField := range flagMap {
2017-02-07 21:33:23 +00:00
if structField . Type . Kind ( ) == reflect . Bool {
flags = append ( flags , f )
}
}
return flags , nil
}
2018-02-26 21:14:03 +00:00
// GetFlags returns flags
2017-02-07 21:33:23 +00:00
func GetFlags ( config interface { } ) ( [ ] string , error ) {
2018-02-26 21:14:03 +00:00
flagMap := make ( map [ string ] reflect . StructField )
if err := getTypesRecursive ( reflect . ValueOf ( config ) , flagMap , "" ) ; err != nil {
2017-02-07 21:33:23 +00:00
return [ ] string { } , err
}
2018-02-26 21:14:03 +00:00
flags := make ( [ ] string , 0 , len ( flagMap ) )
for f := range flagMap {
2017-02-07 21:33:23 +00:00
flags = append ( flags , f )
}
return flags , nil
}
2018-02-26 21:14:03 +00:00
// ParseArgs : parses args return a map[flag]Getter, using parsers map[type]Getter
// args must be formatted as like as flag documentation. See https://golang.org/pkg/flag
func parseArgs ( args [ ] string , flagMap map [ string ] reflect . StructField , parsers map [ reflect . Type ] parse . Parser ) ( map [ string ] parse . Parser , error ) {
newParsers := map [ string ] parse . Parser { }
2017-02-07 21:33:23 +00:00
flagSet := flag . NewFlagSet ( "flaeg.Load" , flag . ContinueOnError )
2018-02-26 21:14:03 +00:00
// Disable output
2017-02-07 21:33:23 +00:00
flagSet . SetOutput ( ioutil . Discard )
2018-02-26 21:14:03 +00:00
2017-02-07 21:33:23 +00:00
var err error
2018-02-26 21:14:03 +00:00
for flg , structField := range flagMap {
2017-02-07 21:33:23 +00:00
if parser , ok := parsers [ structField . Type ] ; ok {
2018-02-26 21:14:03 +00:00
newParserValue := reflect . New ( reflect . TypeOf ( parser ) . Elem ( ) )
newParserValue . Elem ( ) . Set ( reflect . ValueOf ( parser ) . Elem ( ) )
newParser := newParserValue . Interface ( ) . ( parse . Parser )
2017-02-07 21:33:23 +00:00
if short := structField . Tag . Get ( "short" ) ; len ( short ) == 1 {
2018-02-26 21:14:03 +00:00
flagSet . VarP ( newParser , flg , short , structField . Tag . Get ( "description" ) )
2017-02-07 21:33:23 +00:00
} else {
2018-02-26 21:14:03 +00:00
flagSet . Var ( newParser , flg , structField . Tag . Get ( "description" ) )
2017-02-07 21:33:23 +00:00
}
2018-02-26 21:14:03 +00:00
newParsers [ flg ] = newParser
2017-02-07 21:33:23 +00:00
} else {
err = ErrParserNotFound
}
}
// prevents case sensitivity issue
args = argsToLower ( args )
2018-02-26 21:14:03 +00:00
if errParse := flagSet . Parse ( args ) ; errParse != nil {
return nil , errParse
}
// Visitor in flag.Parse
var flagList [ ] * flag . Flag
visitor := func ( fl * flag . Flag ) {
flagList = append ( flagList , fl )
2017-02-07 21:33:23 +00:00
}
2018-02-26 21:14:03 +00:00
// Fill flagList with parsed flags
2017-02-07 21:33:23 +00:00
flagSet . Visit ( visitor )
2018-02-26 21:14:03 +00:00
// Return var
valMap := make ( map [ string ] parse . Parser )
// Return parsers on parsed flag
for _ , flg := range flagList {
valMap [ flg . Name ] = newParsers [ flg . Name ]
2017-02-07 21:33:23 +00:00
}
2018-02-26 21:14:03 +00:00
return valMap , err
2017-02-07 21:33:23 +00:00
}
func getDefaultValue ( defaultValue reflect . Value , defaultPointersValue reflect . Value , defaultValmap map [ string ] reflect . Value , key string ) error {
if defaultValue . Type ( ) != defaultPointersValue . Type ( ) {
2018-02-26 21:14:03 +00:00
return fmt . Errorf ( "parameters defaultValue and defaultPointersValue must be the same struct. defaultValue type: %s is not defaultPointersValue type: %s" , defaultValue . Type ( ) . String ( ) , defaultPointersValue . Type ( ) . String ( ) )
2017-02-07 21:33:23 +00:00
}
2018-02-26 21:14:03 +00:00
2017-02-07 21:33:23 +00:00
name := key
switch defaultValue . Kind ( ) {
case reflect . Struct :
for i := 0 ; i < defaultValue . NumField ( ) ; i ++ {
if defaultValue . Type ( ) . Field ( i ) . Anonymous {
if err := getDefaultValue ( defaultValue . Field ( i ) , defaultPointersValue . Field ( i ) , defaultValmap , name ) ; err != nil {
return err
}
} else if len ( defaultValue . Type ( ) . Field ( i ) . Tag . Get ( "description" ) ) > 0 {
fieldName := defaultValue . Type ( ) . Field ( i ) . Name
if tag := defaultValue . Type ( ) . Field ( i ) . Tag . Get ( "long" ) ; len ( tag ) > 0 {
fieldName = tag
}
2018-02-26 21:14:03 +00:00
2017-02-07 21:33:23 +00:00
if len ( key ) == 0 {
name = strings . ToLower ( fieldName )
} else {
name = key + "." + strings . ToLower ( fieldName )
}
2018-02-26 21:14:03 +00:00
2017-02-07 21:33:23 +00:00
if defaultValue . Field ( i ) . Kind ( ) != reflect . Ptr {
defaultValmap [ name ] = defaultValue . Field ( i )
}
if err := getDefaultValue ( defaultValue . Field ( i ) , defaultPointersValue . Field ( i ) , defaultValmap , name ) ; err != nil {
return err
}
}
}
case reflect . Ptr :
if ! defaultPointersValue . IsNil ( ) {
if len ( key ) != 0 {
2018-02-26 21:14:03 +00:00
// turn ptr fields to nil
2017-02-07 21:33:23 +00:00
defaultPointersNilValue , err := setPointersNil ( defaultPointersValue )
if err != nil {
return err
}
defaultValmap [ name ] = defaultPointersNilValue
}
2018-02-26 21:14:03 +00:00
2017-02-07 21:33:23 +00:00
if ! defaultValue . IsNil ( ) {
if err := getDefaultValue ( defaultValue . Elem ( ) , defaultPointersValue . Elem ( ) , defaultValmap , name ) ; err != nil {
return err
}
} else {
if err := getDefaultValue ( defaultPointersValue . Elem ( ) , defaultPointersValue . Elem ( ) , defaultValmap , name ) ; err != nil {
return err
}
}
} else {
instValue := reflect . New ( defaultPointersValue . Type ( ) . Elem ( ) )
if len ( key ) != 0 {
defaultValmap [ name ] = instValue
}
2018-02-26 21:14:03 +00:00
2017-02-07 21:33:23 +00:00
if ! defaultValue . IsNil ( ) {
if err := getDefaultValue ( defaultValue . Elem ( ) , instValue . Elem ( ) , defaultValmap , name ) ; err != nil {
return err
}
} else {
if err := getDefaultValue ( instValue . Elem ( ) , instValue . Elem ( ) , defaultValmap , name ) ; err != nil {
return err
}
}
}
default :
return nil
}
return nil
}
2018-02-26 21:14:03 +00:00
// objValue a reflect.Value of a not-nil pointer on a struct
2017-02-07 21:33:23 +00:00
func setPointersNil ( objValue reflect . Value ) ( reflect . Value , error ) {
if objValue . Kind ( ) != reflect . Ptr {
2018-02-26 21:14:03 +00:00
return objValue , fmt . Errorf ( "parameters objValue must be a not-nil pointer on a struct, not a %s" , objValue . Kind ( ) )
2017-02-07 21:33:23 +00:00
} else if objValue . IsNil ( ) {
2018-02-26 21:14:03 +00:00
return objValue , errors . New ( "parameters objValue must be a not-nil pointer" )
2017-02-07 21:33:23 +00:00
} else if objValue . Elem ( ) . Kind ( ) != reflect . Struct {
// fmt.Printf("Parameters objValue must be a not-nil pointer on a struct, not a pointer on a %s\n", objValue.Elem().Kind().String())
return objValue , nil
}
2018-02-26 21:14:03 +00:00
// Clone
2017-02-07 21:33:23 +00:00
starObjValue := objValue . Elem ( )
nilPointersObjVal := reflect . New ( starObjValue . Type ( ) )
starNilPointersObjVal := nilPointersObjVal . Elem ( )
starNilPointersObjVal . Set ( starObjValue )
for i := 0 ; i < nilPointersObjVal . Elem ( ) . NumField ( ) ; i ++ {
if field := nilPointersObjVal . Elem ( ) . Field ( i ) ; field . Kind ( ) == reflect . Ptr && field . CanSet ( ) {
field . Set ( reflect . Zero ( field . Type ( ) ) )
}
}
return nilPointersObjVal , nil
}
2018-02-26 21:14:03 +00:00
// FillStructRecursive initialize a value of any tagged Struct given by reference
func fillStructRecursive ( objValue reflect . Value , defaultPointerValMap map [ string ] reflect . Value , valMap map [ string ] parse . Parser , key string ) error {
2017-02-07 21:33:23 +00:00
name := key
switch objValue . Kind ( ) {
case reflect . Struct :
for i := 0 ; i < objValue . Type ( ) . NumField ( ) ; i ++ {
if objValue . Type ( ) . Field ( i ) . Anonymous {
2018-02-26 21:14:03 +00:00
if err := fillStructRecursive ( objValue . Field ( i ) , defaultPointerValMap , valMap , name ) ; err != nil {
2017-02-07 21:33:23 +00:00
return err
}
} else if len ( objValue . Type ( ) . Field ( i ) . Tag . Get ( "description" ) ) > 0 {
fieldName := objValue . Type ( ) . Field ( i ) . Name
if tag := objValue . Type ( ) . Field ( i ) . Tag . Get ( "long" ) ; len ( tag ) > 0 {
fieldName = tag
}
2018-02-26 21:14:03 +00:00
2017-02-07 21:33:23 +00:00
if len ( key ) == 0 {
name = strings . ToLower ( fieldName )
} else {
name = key + "." + strings . ToLower ( fieldName )
}
2018-02-26 21:14:03 +00:00
if objValue . Field ( i ) . Kind ( ) != reflect . Ptr {
if val , ok := valMap [ name ] ; ok {
2017-02-07 21:33:23 +00:00
if err := setFields ( objValue . Field ( i ) , val ) ; err != nil {
return err
}
}
}
2018-02-26 21:14:03 +00:00
if err := fillStructRecursive ( objValue . Field ( i ) , defaultPointerValMap , valMap , name ) ; err != nil {
2017-02-07 21:33:23 +00:00
return err
}
}
}
case reflect . Ptr :
if len ( key ) == 0 && ! objValue . IsNil ( ) {
2018-02-26 21:14:03 +00:00
return fillStructRecursive ( objValue . Elem ( ) , defaultPointerValMap , valMap , name )
2017-02-07 21:33:23 +00:00
}
2018-02-26 21:14:03 +00:00
2017-02-07 21:33:23 +00:00
contains := false
2018-02-26 21:14:03 +00:00
for flg := range valMap {
2017-02-07 21:33:23 +00:00
// TODO replace by regexp
2018-02-26 21:14:03 +00:00
if strings . HasPrefix ( flg , name + "." ) {
2017-02-07 21:33:23 +00:00
contains = true
break
}
}
2018-02-26 21:14:03 +00:00
2017-02-07 21:33:23 +00:00
needDefault := false
2018-02-26 21:14:03 +00:00
if _ , ok := valMap [ name ] ; ok {
needDefault = valMap [ name ] . Get ( ) . ( bool )
2017-02-07 21:33:23 +00:00
}
if contains && objValue . IsNil ( ) {
needDefault = true
}
if needDefault {
2018-02-26 21:14:03 +00:00
if defVal , ok := defaultPointerValMap [ name ] ; ok {
// set default pointer value
2017-02-07 21:33:23 +00:00
objValue . Set ( defVal )
} else {
return fmt . Errorf ( "flag %s default value not provided" , name )
}
}
2018-02-26 21:14:03 +00:00
2017-02-07 21:33:23 +00:00
if ! objValue . IsNil ( ) && contains {
if objValue . Type ( ) . Elem ( ) . Kind ( ) == reflect . Struct {
2018-02-26 21:14:03 +00:00
if err := fillStructRecursive ( objValue . Elem ( ) , defaultPointerValMap , valMap , name ) ; err != nil {
2017-02-07 21:33:23 +00:00
return err
}
}
}
default :
return nil
}
return nil
}
2018-02-26 21:14:03 +00:00
// SetFields sets value to fieldValue using tag as key in valMap
func setFields ( fieldValue reflect . Value , val parse . Parser ) error {
2017-02-07 21:33:23 +00:00
if fieldValue . CanSet ( ) {
fieldValue . Set ( reflect . ValueOf ( val ) . Elem ( ) . Convert ( fieldValue . Type ( ) ) )
} else {
2018-02-26 21:14:03 +00:00
return fmt . Errorf ( "%s is not settable" , fieldValue . Type ( ) . String ( ) )
2017-02-07 21:33:23 +00:00
}
return nil
}
2018-02-26 21:14:03 +00:00
// PrintHelp generates and prints command line help
func PrintHelp ( flagMap map [ string ] reflect . StructField , defaultValmap map [ string ] reflect . Value , parsers map [ reflect . Type ] parse . Parser ) error {
return PrintHelpWithCommand ( flagMap , defaultValmap , parsers , nil , nil )
2017-02-07 21:33:23 +00:00
}
2018-02-26 21:14:03 +00:00
// PrintError takes a not nil error and prints command line help
func PrintError ( err error , flagMap map [ string ] reflect . StructField , defaultValmap map [ string ] reflect . Value , parsers map [ reflect . Type ] parse . Parser ) error {
2017-02-07 21:33:23 +00:00
if err != flag . ErrHelp {
2018-02-26 21:14:03 +00:00
fmt . Printf ( "Error: %s\n" , err )
2017-02-07 21:33:23 +00:00
}
if ! strings . Contains ( err . Error ( ) , ":No parser for type" ) {
2018-02-26 21:14:03 +00:00
PrintHelp ( flagMap , defaultValmap , parsers )
2017-02-07 21:33:23 +00:00
}
return err
}
2018-02-26 21:14:03 +00:00
// LoadWithParsers initializes config : struct fields given by reference, with args : arguments.
// Some custom parsers may be given.
func LoadWithParsers ( config interface { } , defaultValue interface { } , args [ ] string , customParsers map [ reflect . Type ] parse . Parser ) error {
2017-02-07 21:33:23 +00:00
cmd := & Command {
Config : config ,
DefaultPointersConfig : defaultValue ,
}
_ , cmd . Name = path . Split ( os . Args [ 0 ] )
return LoadWithCommand ( cmd , args , customParsers , nil )
}
2018-02-26 21:14:03 +00:00
// Load initializes config : struct fields given by reference, with args : arguments.
// Some custom parsers may be given.
2017-02-07 21:33:23 +00:00
func Load ( config interface { } , defaultValue interface { } , args [ ] string ) error {
return LoadWithParsers ( config , defaultValue , args , nil )
}
// Command structure contains program/command information (command name and description)
// Config must be a pointer on the configuration struct to parse (it contains default values of field)
// DefaultPointersConfig contains default pointers values: those values are set on pointers fields if their flags are called
// It must be the same type(struct) as Config
// Run is the func which launch the program using initialized configuration structure
type Command struct {
Name string
Description string
Config interface { }
2018-02-26 21:14:03 +00:00
DefaultPointersConfig interface { } // TODO: case DefaultPointersConfig is nil
2017-02-07 21:33:23 +00:00
Run func ( ) error
Metadata map [ string ] string
}
2018-02-26 21:14:03 +00:00
// LoadWithCommand initializes config : struct fields given by reference, with args : arguments.
// Some custom parsers and some subCommand may be given.
func LoadWithCommand ( cmd * Command , cmdArgs [ ] string , customParsers map [ reflect . Type ] parse . Parser , subCommand [ ] * Command ) error {
parsers , err := parse . LoadParsers ( customParsers )
2017-02-07 21:33:23 +00:00
if err != nil {
return err
}
2018-02-26 21:14:03 +00:00
tagsMap := make ( map [ string ] reflect . StructField )
if err := getTypesRecursive ( reflect . ValueOf ( cmd . Config ) , tagsMap , "" ) ; err != nil {
2017-02-07 21:33:23 +00:00
return err
}
2018-02-26 21:14:03 +00:00
defaultValMap := make ( map [ string ] reflect . Value )
if err := getDefaultValue ( reflect . ValueOf ( cmd . Config ) , reflect . ValueOf ( cmd . DefaultPointersConfig ) , defaultValMap , "" ) ; err != nil {
2017-02-07 21:33:23 +00:00
return err
}
2018-02-26 21:14:03 +00:00
valMap , errParseArgs := parseArgs ( cmdArgs , tagsMap , parsers )
2017-02-07 21:33:23 +00:00
if errParseArgs != nil && errParseArgs != ErrParserNotFound {
2018-02-26 21:14:03 +00:00
return PrintErrorWithCommand ( errParseArgs , tagsMap , defaultValMap , parsers , cmd , subCommand )
2017-02-07 21:33:23 +00:00
}
2018-02-26 21:14:03 +00:00
if err := fillStructRecursive ( reflect . ValueOf ( cmd . Config ) , defaultValMap , valMap , "" ) ; err != nil {
2017-02-07 21:33:23 +00:00
return err
}
if errParseArgs == ErrParserNotFound {
return errParseArgs
}
return nil
}
2018-02-26 21:14:03 +00:00
// PrintHelpWithCommand generates and prints command line help for a Command
func PrintHelpWithCommand ( flagMap map [ string ] reflect . StructField , defaultValMap map [ string ] reflect . Value , parsers map [ reflect . Type ] parse . Parser , cmd * Command , subCmd [ ] * Command ) error {
2017-02-07 21:33:23 +00:00
// Define a templates
// Using POSXE STD : http://pubs.opengroup.org/onlinepubs/9699919799/
const helper = ` { { if . ProgDescription } } { { . ProgDescription } }
2017-03-27 21:15:48 +00:00
2017-02-07 21:33:23 +00:00
{ { end } } Usage : { { . ProgName } } [ -- flag = flag_argument ] [ - f [ flag_argument ] ] ... set flag_argument to flag ( s )
or : { { . ProgName } } [ -- flag [ = true | false | ] ] [ - f [ true | false | ] ] ... set true / false to boolean flag ( s )
{ { if . SubCommands } }
Available Commands : { { range $ subCmdName , $ subCmdDesc := . SubCommands } }
{ { printf "\t%-50s %s" $ subCmdName $ subCmdDesc } } { { end } }
Use "{{.ProgName}} [command] --help" for more information about a command .
{ { end } }
Flags :
`
// Use a struct to give data to template
type TempStruct struct {
ProgName string
ProgDescription string
SubCommands map [ string ] string
}
tempStruct := TempStruct { }
if cmd != nil {
tempStruct . ProgName = cmd . Name
tempStruct . ProgDescription = cmd . Description
tempStruct . SubCommands = map [ string ] string { }
if len ( subCmd ) > 1 && cmd == subCmd [ 0 ] {
for _ , c := range subCmd [ 1 : ] {
tempStruct . SubCommands [ c . Name ] = c . Description
}
}
} else {
_ , tempStruct . ProgName = path . Split ( os . Args [ 0 ] )
}
2018-02-26 21:14:03 +00:00
// Run Template
2017-02-07 21:33:23 +00:00
tmplHelper , err := template . New ( "helper" ) . Parse ( helper )
if err != nil {
return err
}
err = tmplHelper . Execute ( os . Stdout , tempStruct )
if err != nil {
return err
}
2018-02-26 21:14:03 +00:00
return printFlagsDescriptionsDefaultValues ( flagMap , defaultValMap , parsers , os . Stdout )
2017-02-07 21:33:23 +00:00
}
2018-02-26 21:14:03 +00:00
func printFlagsDescriptionsDefaultValues ( flagMap map [ string ] reflect . StructField , defaultValMap map [ string ] reflect . Value , parsers map [ reflect . Type ] parse . Parser , output io . Writer ) error {
2017-02-07 21:33:23 +00:00
// Sort alphabetically & Delete unparsable flags in a slice
2018-02-26 21:14:03 +00:00
var flags [ ] string
for flg , field := range flagMap {
2017-02-07 21:33:23 +00:00
if _ , ok := parsers [ field . Type ] ; ok {
2018-02-26 21:14:03 +00:00
flags = append ( flags , flg )
2017-02-07 21:33:23 +00:00
}
}
sort . Strings ( flags )
// Process data
2018-02-26 21:14:03 +00:00
var descriptions [ ] string
var defaultValues [ ] string
var flagsWithDash [ ] string
var shortFlagsWithDash [ ] string
for _ , flg := range flags {
field := flagMap [ flg ]
2017-02-07 21:33:23 +00:00
if short := field . Tag . Get ( "short" ) ; len ( short ) == 1 {
shortFlagsWithDash = append ( shortFlagsWithDash , "-" + short + "," )
} else {
shortFlagsWithDash = append ( shortFlagsWithDash , "" )
}
2018-02-26 21:14:03 +00:00
flagsWithDash = append ( flagsWithDash , "--" + flg )
2017-02-07 21:33:23 +00:00
2018-02-26 21:14:03 +00:00
// flag on pointer ?
if defVal , ok := defaultValMap [ flg ] ; ok {
2017-02-07 21:33:23 +00:00
if defVal . Kind ( ) != reflect . Ptr {
// Set defaultValue on parsers
2018-02-26 21:14:03 +00:00
parsers [ field . Type ] . SetValue ( defaultValMap [ flg ] . Interface ( ) )
2017-02-07 21:33:23 +00:00
}
if defVal := parsers [ field . Type ] . String ( ) ; len ( defVal ) > 0 {
defaultValues = append ( defaultValues , fmt . Sprintf ( "(default \"%s\")" , defVal ) )
} else {
defaultValues = append ( defaultValues , "" )
}
}
splittedDescriptions := split ( field . Tag . Get ( "description" ) , 80 )
for i , description := range splittedDescriptions {
descriptions = append ( descriptions , description )
if i != 0 {
defaultValues = append ( defaultValues , "" )
2018-02-26 21:14:03 +00:00
flagsWithDash = append ( flagsWithDash , "" )
2017-02-07 21:33:23 +00:00
shortFlagsWithDash = append ( shortFlagsWithDash , "" )
}
}
}
2018-02-26 21:14:03 +00:00
2017-02-07 21:33:23 +00:00
//add help flag
shortFlagsWithDash = append ( shortFlagsWithDash , "-h," )
2018-02-26 21:14:03 +00:00
flagsWithDash = append ( flagsWithDash , "--help" )
2017-02-07 21:33:23 +00:00
descriptions = append ( descriptions , "Print Help (this message) and exit" )
defaultValues = append ( defaultValues , "" )
2018-02-26 21:14:03 +00:00
return displayTab ( output , shortFlagsWithDash , flagsWithDash , descriptions , defaultValues )
2017-02-07 21:33:23 +00:00
}
func split ( str string , width int ) [ ] string {
if len ( str ) > width {
index := strings . LastIndex ( str [ : width ] , " " )
if index == - 1 {
index = width
}
2018-02-26 21:14:03 +00:00
2017-02-07 21:33:23 +00:00
return append ( [ ] string { strings . TrimSpace ( str [ : index ] ) } , split ( strings . TrimSpace ( str [ index : ] ) , width ) ... )
}
return [ ] string { str }
}
func displayTab ( output io . Writer , columns ... [ ] string ) error {
w := new ( tabwriter . Writer )
w . Init ( output , 0 , 4 , 1 , ' ' , 0 )
2018-02-26 21:14:03 +00:00
nbRow := len ( columns [ 0 ] )
nbCol := len ( columns )
2017-02-07 21:33:23 +00:00
for i := 0 ; i < nbRow ; i ++ {
row := ""
for j , col := range columns {
row += col [ i ]
if j != nbCol - 1 {
row += "\t"
}
}
fmt . Fprintln ( w , row )
}
2018-02-26 21:14:03 +00:00
return w . Flush ( )
2017-02-07 21:33:23 +00:00
}
2018-02-26 21:14:03 +00:00
// PrintErrorWithCommand takes a not nil error and prints command line help
func PrintErrorWithCommand ( err error , flagMap map [ string ] reflect . StructField , defaultValMap map [ string ] reflect . Value , parsers map [ reflect . Type ] parse . Parser , cmd * Command , subCmd [ ] * Command ) error {
2017-02-07 21:33:23 +00:00
if err != flag . ErrHelp {
fmt . Printf ( "Error here : %s\n" , err )
}
2018-02-26 21:14:03 +00:00
PrintHelpWithCommand ( flagMap , defaultValMap , parsers , cmd , subCmd )
2017-02-07 21:33:23 +00:00
return err
}
2018-02-26 21:14:03 +00:00
// Flaeg struct contains commands (at least the root one)
// and row arguments (command and/or flags)
// a map of custom parsers could be use
2017-02-07 21:33:23 +00:00
type Flaeg struct {
calledCommand * Command
commands [ ] * Command ///rootCommand is th fist one in this slice
args [ ] string
2018-02-26 21:14:03 +00:00
commandArgs [ ] string
customParsers map [ reflect . Type ] parse . Parser
2017-02-07 21:33:23 +00:00
}
2018-02-26 21:14:03 +00:00
// New creates and initialize a pointer on Flaeg
2017-02-07 21:33:23 +00:00
func New ( rootCommand * Command , args [ ] string ) * Flaeg {
var f Flaeg
f . commands = [ ] * Command { rootCommand }
f . args = args
2018-02-26 21:14:03 +00:00
f . customParsers = map [ reflect . Type ] parse . Parser { }
2017-02-07 21:33:23 +00:00
return & f
}
2018-02-26 21:14:03 +00:00
// AddCommand adds sub-command to the root command
2017-02-07 21:33:23 +00:00
func ( f * Flaeg ) AddCommand ( command * Command ) {
f . commands = append ( f . commands , command )
}
2018-02-26 21:14:03 +00:00
// AddParser adds custom parser for a type to the map of custom parsers
func ( f * Flaeg ) AddParser ( typ reflect . Type , parser parse . Parser ) {
2017-02-07 21:33:23 +00:00
f . customParsers [ typ ] = parser
}
2018-02-26 21:14:03 +00:00
// Run calls the command with flags given as arguments
2017-02-07 21:33:23 +00:00
func ( f * Flaeg ) Run ( ) error {
if f . calledCommand == nil {
if _ , _ , err := f . findCommandWithCommandArgs ( ) ; err != nil {
return err
}
}
2018-02-26 21:14:03 +00:00
2017-02-07 21:33:23 +00:00
if _ , err := f . Parse ( f . calledCommand ) ; err != nil {
return err
}
return f . calledCommand . Run ( )
}
// Parse calls Flaeg Load Function end returns the parsed command structure (by reference)
// It returns nil and a not nil error if it fails
func ( f * Flaeg ) Parse ( cmd * Command ) ( * Command , error ) {
if f . calledCommand == nil {
2018-02-26 21:14:03 +00:00
f . commandArgs = f . args
2017-02-07 21:33:23 +00:00
}
2018-02-26 21:14:03 +00:00
if err := LoadWithCommand ( cmd , f . commandArgs , f . customParsers , f . commands ) ; err != nil {
2017-02-07 21:33:23 +00:00
return cmd , err
}
return cmd , nil
}
2018-02-26 21:14:03 +00:00
// splitArgs takes args (type []string) and return command ("" if rootCommand) and command's args
2017-02-07 21:33:23 +00:00
func splitArgs ( args [ ] string ) ( string , [ ] string ) {
if len ( args ) >= 1 && len ( args [ 0 ] ) >= 1 && string ( args [ 0 ] [ 0 ] ) != "-" {
if len ( args ) == 1 {
return strings . ToLower ( args [ 0 ] ) , [ ] string { }
}
return strings . ToLower ( args [ 0 ] ) , args [ 1 : ]
}
return "" , args
}
// findCommandWithCommandArgs returns the called command (by reference) and command's args
// the error returned is not nil if it fails
func ( f * Flaeg ) findCommandWithCommandArgs ( ) ( * Command , [ ] string , error ) {
2018-02-26 21:14:03 +00:00
var commandName string
commandName , f . commandArgs = splitArgs ( f . args )
2017-02-07 21:33:23 +00:00
if len ( commandName ) > 0 {
for _ , command := range f . commands {
if commandName == command . Name {
f . calledCommand = command
2018-02-26 21:14:03 +00:00
return f . calledCommand , f . commandArgs , nil
2017-02-07 21:33:23 +00:00
}
}
2018-02-26 21:14:03 +00:00
return nil , [ ] string { } , fmt . Errorf ( "command %s not found" , commandName )
2017-02-07 21:33:23 +00:00
}
f . calledCommand = f . commands [ 0 ]
2018-02-26 21:14:03 +00:00
return f . calledCommand , f . commandArgs , nil
2017-02-07 21:33:23 +00:00
}
// GetCommand splits args and returns the called command (by reference)
// It returns nil and a not nil error if it fails
func ( f * Flaeg ) GetCommand ( ) ( * Command , error ) {
if f . calledCommand == nil {
_ , _ , err := f . findCommandWithCommandArgs ( )
return f . calledCommand , err
}
return f . calledCommand , nil
}
2018-02-26 21:14:03 +00:00
// isExported return true is the field (from fieldName) is exported,
// else false
2017-02-07 21:33:23 +00:00
func isExported ( fieldName string ) bool {
if len ( fieldName ) < 1 {
return false
}
2018-02-26 21:14:03 +00:00
2017-02-07 21:33:23 +00:00
if string ( fieldName [ 0 ] ) == strings . ToUpper ( string ( fieldName [ 0 ] ) ) {
return true
}
2018-02-26 21:14:03 +00:00
2017-02-07 21:33:23 +00:00
return false
}
func argToLower ( inArg string ) string {
if len ( inArg ) < 2 {
return strings . ToLower ( inArg )
}
2018-02-26 21:14:03 +00:00
2017-02-07 21:33:23 +00:00
var outArg string
dashIndex := strings . Index ( inArg , "--" )
if dashIndex == - 1 {
if dashIndex = strings . Index ( inArg , "-" ) ; dashIndex == - 1 {
return inArg
}
2018-02-26 21:14:03 +00:00
// -fValue
2017-02-07 21:33:23 +00:00
outArg = strings . ToLower ( inArg [ dashIndex : dashIndex + 2 ] ) + inArg [ dashIndex + 2 : ]
return outArg
}
2018-02-26 21:14:03 +00:00
// --flag
2017-02-07 21:33:23 +00:00
if equalIndex := strings . Index ( inArg , "=" ) ; equalIndex != - 1 {
2018-02-26 21:14:03 +00:00
// --flag=value
2017-02-07 21:33:23 +00:00
outArg = strings . ToLower ( inArg [ dashIndex : equalIndex ] ) + inArg [ equalIndex : ]
} else {
2018-02-26 21:14:03 +00:00
// --boolflag
2017-02-07 21:33:23 +00:00
outArg = strings . ToLower ( inArg [ dashIndex : ] )
}
return outArg
}
func argsToLower ( inArgs [ ] string ) [ ] string {
2018-02-26 21:14:03 +00:00
outArgs := make ( [ ] string , len ( inArgs ) )
2017-02-07 21:33:23 +00:00
for i , inArg := range inArgs {
outArgs [ i ] = argToLower ( inArg )
}
return outArgs
}