diff --git a/CHANGELOG.md b/CHANGELOG.md index 34ae7cb5c..2833b3490 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Change Log +## [v1.5.3](https://github.com/containous/traefik/tree/v1.5.3) (2018-02-27) +[All Commits](https://github.com/containous/traefik/compare/v1.5.2...v1.5.3) + +**Bug fixes:** +- **[acme]** Check all the C/N and SANs of provided certificates before generating ACME certificates ([#2913](https://github.com/containous/traefik/pull/2913) by [nmengin](https://github.com/nmengin)) +- **[docker/swarm]** Empty IP address when use endpoint mode dnsrr ([#2887](https://github.com/containous/traefik/pull/2887) by [mmatur](https://github.com/mmatur)) +- **[middleware]** Infinite entry point redirection. ([#2929](https://github.com/containous/traefik/pull/2929) by [ldez](https://github.com/ldez)) +- **[provider]** Isolate backend with same name on different provider ([#2862](https://github.com/containous/traefik/pull/2862) by [Juliens](https://github.com/Juliens)) +- **[tls]** Starting Træfik even if TLS certificates are in error ([#2909](https://github.com/containous/traefik/pull/2909) by [nmengin](https://github.com/nmengin)) +- **[tls]** Add DEBUG log when no provided certificate can check a domain ([#2938](https://github.com/containous/traefik/pull/2938) by [nmengin](https://github.com/nmengin)) +- **[webui]** Smooth dashboard refresh. ([#2871](https://github.com/containous/traefik/pull/2871) by [ldez](https://github.com/ldez)) +- Fix Duration JSON unmarshal ([#2935](https://github.com/containous/traefik/pull/2935) by [ldez](https://github.com/ldez)) +- Default value for lifecycle ([#2934](https://github.com/containous/traefik/pull/2934) by [Juliens](https://github.com/Juliens)) +- Check ping configuration. ([#2852](https://github.com/containous/traefik/pull/2852) by [ldez](https://github.com/ldez)) + +**Documentation:** +- **[docker]** it's -> its ([#2901](https://github.com/containous/traefik/pull/2901) by [piec](https://github.com/piec)) +- **[tls]** Fix doc cipher suites ([#2894](https://github.com/containous/traefik/pull/2894) by [emilevauge](https://github.com/emilevauge)) +- Add a CLI help command for Docker. ([#2921](https://github.com/containous/traefik/pull/2921) by [ldez](https://github.com/ldez)) +- Fix traffic pronounce dead link ([#2870](https://github.com/containous/traefik/pull/2870) by [emilevauge](https://github.com/emilevauge)) +- Update documentation on onHostRule, ping examples, and web deprecation ([#2863](https://github.com/containous/traefik/pull/2863) by [Juliens](https://github.com/Juliens)) + ## [v1.5.2](https://github.com/containous/traefik/tree/v1.5.2) (2018-02-12) [All Commits](https://github.com/containous/traefik/compare/v1.5.1...v1.5.2) diff --git a/Gopkg.lock b/Gopkg.lock index fc287915c..06fb30e24 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -225,9 +225,12 @@ [[projects]] name = "github.com/containous/flaeg" - packages = ["."] - revision = "60c87a513a955ca7225e1b1c772581cea8420cb4" - version = "v1.0.1" + packages = [ + ".", + "parse" + ] + revision = "963366c29a7acc2d6e02f4f9bcf260d5a1cf4968" + version = "v1.1.1" [[projects]] branch = "master" diff --git a/acme/account.go b/acme/account.go index 425f63489..9b7194bda 100644 --- a/acme/account.go +++ b/acme/account.go @@ -222,6 +222,24 @@ func (dc *DomainsCertificates) exists(domainToFind Domain) (*DomainsCertificate, return nil, false } +func (dc *DomainsCertificates) toDomainsMap() map[string]*tls.Certificate { + domainsCertificatesMap := make(map[string]*tls.Certificate) + for _, domainCertificate := range dc.Certs { + certKey := domainCertificate.Domains.Main + if domainCertificate.Domains.SANs != nil { + sort.Strings(domainCertificate.Domains.SANs) + for _, dnsName := range domainCertificate.Domains.SANs { + if dnsName != domainCertificate.Domains.Main { + certKey += fmt.Sprintf(",%s", dnsName) + } + } + + } + domainsCertificatesMap[certKey] = domainCertificate.tlsCert + } + return domainsCertificatesMap +} + // DomainsCertificate contains a certificate for multiple domains type DomainsCertificate struct { Domains Domain diff --git a/acme/acme.go b/acme/acme.go index 86bce6a37..f9592ac72 100644 --- a/acme/acme.go +++ b/acme/acme.go @@ -391,7 +391,7 @@ func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificat domain := types.CanonicalDomain(clientHello.ServerName) account := a.store.Get().(*Account) - if providedCertificate := a.getProvidedCertificate([]string{domain}); providedCertificate != nil { + if providedCertificate := a.getProvidedCertificate(domain); providedCertificate != nil { return providedCertificate, nil } @@ -625,11 +625,6 @@ func (a *ACME) LoadCertificateForDomains(domains []string) { domains = fun.Map(types.CanonicalDomain, domains).([]string) - // Check provided certificates - if a.getProvidedCertificate(domains) != nil { - return - } - operation := func() error { if a.client == nil { return errors.New("ACME client still not built") @@ -647,32 +642,34 @@ func (a *ACME) LoadCertificateForDomains(domains []string) { return } account := a.store.Get().(*Account) - var domain Domain - if len(domains) > 1 { - domain = Domain{Main: domains[0], SANs: domains[1:]} - } else { - domain = Domain{Main: domains[0]} - } - if _, exists := account.DomainsCertificate.exists(domain); exists { - // domain already exists + + // Check provided certificates + uncheckedDomains := a.getUncheckedDomains(domains, account) + if len(uncheckedDomains) == 0 { return } - certificate, err := a.getDomainsCertificates(domains) + certificate, err := a.getDomainsCertificates(uncheckedDomains) if err != nil { - log.Errorf("Error getting ACME certificates %+v : %v", domains, err) + log.Errorf("Error getting ACME certificates %+v : %v", uncheckedDomains, err) return } - log.Debugf("Got certificate for domains %+v", domains) + log.Debugf("Got certificate for domains %+v", uncheckedDomains) transaction, object, err := a.store.Begin() if err != nil { - log.Errorf("Error creating transaction %+v : %v", domains, err) + log.Errorf("Error creating transaction %+v : %v", uncheckedDomains, err) return } + var domain Domain + if len(uncheckedDomains) > 1 { + domain = Domain{Main: uncheckedDomains[0], SANs: uncheckedDomains[1:]} + } else { + domain = Domain{Main: uncheckedDomains[0]} + } account = object.(*Account) _, err = account.DomainsCertificate.addCertificateForDomains(certificate, domain) if err != nil { - log.Errorf("Error adding ACME certificates %+v : %v", domains, err) + log.Errorf("Error adding ACME certificates %+v : %v", uncheckedDomains, err) return } if err = transaction.Commit(account); err != nil { @@ -684,36 +681,95 @@ func (a *ACME) LoadCertificateForDomains(domains []string) { // Get provided certificate which check a domains list (Main and SANs) // from static and dynamic provided certificates -func (a *ACME) getProvidedCertificate(domains []string) *tls.Certificate { +func (a *ACME) getProvidedCertificate(domains string) *tls.Certificate { log.Debugf("Looking for provided certificate to validate %s...", domains) cert := searchProvidedCertificateForDomains(domains, a.TLSConfig.NameToCertificate) if cert == nil && a.dynamicCerts != nil && a.dynamicCerts.Get() != nil { cert = searchProvidedCertificateForDomains(domains, a.dynamicCerts.Get().(*traefikTls.DomainsCertificates).Get().(map[string]*tls.Certificate)) } - log.Debugf("No provided certificate found for domains %s, get ACME certificate.", domains) + if cert == nil { + log.Debugf("No provided certificate found for domains %s, get ACME certificate.", domains) + } return cert } -func searchProvidedCertificateForDomains(domains []string, certs map[string]*tls.Certificate) *tls.Certificate { +func searchProvidedCertificateForDomains(domain string, certs map[string]*tls.Certificate) *tls.Certificate { // Use regex to test for provided certs that might have been added into TLSConfig - providedCertMatch := false - for k := range certs { - selector := "^" + strings.Replace(k, "*.", "[^\\.]*\\.?", -1) + "$" - for _, domainToCheck := range domains { - providedCertMatch, _ = regexp.MatchString(selector, domainToCheck) - if !providedCertMatch { + for certDomains := range certs { + domainCheck := false + for _, certDomain := range strings.Split(certDomains, ",") { + selector := "^" + strings.Replace(certDomain, "*.", "[^\\.]*\\.?", -1) + "$" + domainCheck, _ = regexp.MatchString(selector, domain) + if domainCheck { break } } - if providedCertMatch { - log.Debugf("Got provided certificate for domains %s", domains) - return certs[k] - + if domainCheck { + log.Debugf("Domain %q checked by provided certificate %q", domain, certDomains) + return certs[certDomains] } } return nil } +// Get provided certificate which check a domains list (Main and SANs) +// from static and dynamic provided certificates +func (a *ACME) getUncheckedDomains(domains []string, account *Account) []string { + log.Debugf("Looking for provided certificate to validate %s...", domains) + allCerts := make(map[string]*tls.Certificate) + + // Get static certificates + for domains, certificate := range a.TLSConfig.NameToCertificate { + allCerts[domains] = certificate + } + + // Get dynamic certificates + if a.dynamicCerts != nil && a.dynamicCerts.Get() != nil { + for domains, certificate := range a.dynamicCerts.Get().(*traefikTls.DomainsCertificates).Get().(map[string]*tls.Certificate) { + allCerts[domains] = certificate + } + } + + // Get ACME certificates + if account != nil { + for domains, certificate := range account.DomainsCertificate.toDomainsMap() { + allCerts[domains] = certificate + } + } + + return searchUncheckedDomains(domains, allCerts) +} + +func searchUncheckedDomains(domains []string, certs map[string]*tls.Certificate) []string { + uncheckedDomains := []string{} + for _, domainToCheck := range domains { + domainCheck := false + for certDomains := range certs { + domainCheck = false + for _, certDomain := range strings.Split(certDomains, ",") { + // Use regex to test for provided certs that might have been added into TLSConfig + selector := "^" + strings.Replace(certDomain, "*.", "[^\\.]*\\.?", -1) + "$" + domainCheck, _ = regexp.MatchString(selector, domainToCheck) + if domainCheck { + break + } + } + if domainCheck { + break + } + } + if !domainCheck { + uncheckedDomains = append(uncheckedDomains, domainToCheck) + } + } + if len(uncheckedDomains) == 0 { + log.Debugf("No ACME certificate to generate for domains %q.", domains) + } else { + log.Debugf("Domains %q need ACME certificates generation for domains %q.", domains, strings.Join(uncheckedDomains, ",")) + } + return uncheckedDomains +} + func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) { domains = fun.Map(types.CanonicalDomain, domains).([]string) log.Debugf("Loading ACME certificates %s...", domains) diff --git a/acme/acme_test.go b/acme/acme_test.go index 3498e7b9f..db828e708 100644 --- a/acme/acme_test.go +++ b/acme/acme_test.go @@ -281,7 +281,7 @@ cijFkALeQp/qyeXdFld2v9gUN3eCgljgcl0QweRoIc=---`) } } -func TestAcme_getProvidedCertificate(t *testing.T) { +func TestAcme_getUncheckedCertificates(t *testing.T) { mm := make(map[string]*tls.Certificate) mm["*.containo.us"] = &tls.Certificate{} mm["traefik.acme.io"] = &tls.Certificate{} @@ -289,9 +289,36 @@ func TestAcme_getProvidedCertificate(t *testing.T) { a := ACME{TLSConfig: &tls.Config{NameToCertificate: mm}} domains := []string{"traefik.containo.us", "trae.containo.us"} - certificate := a.getProvidedCertificate(domains) - assert.NotNil(t, certificate) + uncheckedDomains := a.getUncheckedDomains(domains, nil) + assert.Empty(t, uncheckedDomains) domains = []string{"traefik.acme.io", "trae.acme.io"} - certificate = a.getProvidedCertificate(domains) + uncheckedDomains = a.getUncheckedDomains(domains, nil) + assert.Len(t, uncheckedDomains, 1) + domainsCertificates := DomainsCertificates{Certs: []*DomainsCertificate{ + { + tlsCert: &tls.Certificate{}, + Domains: Domain{ + Main: "*.acme.wtf", + SANs: []string{"trae.acme.io"}, + }, + }, + }} + account := Account{DomainsCertificate: domainsCertificates} + uncheckedDomains = a.getUncheckedDomains(domains, &account) + assert.Empty(t, uncheckedDomains) +} + +func TestAcme_getProvidedCertificate(t *testing.T) { + mm := make(map[string]*tls.Certificate) + mm["*.containo.us"] = &tls.Certificate{} + mm["traefik.acme.io"] = &tls.Certificate{} + + a := ACME{TLSConfig: &tls.Config{NameToCertificate: mm}} + + domain := "traefik.containo.us" + certificate := a.getProvidedCertificate(domain) + assert.NotNil(t, certificate) + domain = "trae.acme.io" + certificate = a.getProvidedCertificate(domain) assert.Nil(t, certificate) } diff --git a/cmd/traefik/configuration.go b/cmd/traefik/configuration.go index 514510c94..dae3d1ab5 100644 --- a/cmd/traefik/configuration.go +++ b/cmd/traefik/configuration.go @@ -308,6 +308,9 @@ func NewTraefikConfiguration() *TraefikConfiguration { HealthCheck: &configuration.HealthCheckConfig{ Interval: flaeg.Duration(configuration.DefaultHealthCheckInterval), }, + LifeCycle: &configuration.LifeCycle{ + GraceTimeOut: flaeg.Duration(configuration.DefaultGraceTimeout), + }, CheckNewVersion: true, }, ConfigFile: "", diff --git a/docs/basics.md b/docs/basics.md index 0e20bb66e..bc85ae021 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -569,6 +569,11 @@ Each command is described at the beginning of the help section: ```bash traefik --help + +# or + +docker run traefik[:version] --help +# ex: docker run traefik:1.5 --help ``` ### Command: bug diff --git a/docs/user-guide/docker-and-lets-encrypt.md b/docs/user-guide/docker-and-lets-encrypt.md index 68c11950d..1e415251d 100644 --- a/docs/user-guide/docker-and-lets-encrypt.md +++ b/docs/user-guide/docker-and-lets-encrypt.md @@ -69,7 +69,7 @@ networks: ``` As you can see, we're mounting the `traefik.toml` file as well as the (empty) `acme.json` file in the container. -Also, we're mounting the `/var/run/docker.sock` Docker socket in the container as well, so Træfik can listen to Docker events and reconfigure it's own internal configuration when containers are created (or shut down). +Also, we're mounting the `/var/run/docker.sock` Docker socket in the container as well, so Træfik can listen to Docker events and reconfigure its own internal configuration when containers are created (or shut down). Also, we're making sure the container is automatically restarted by the Docker engine in case of problems (or: if the server is rebooted). We're publishing the default HTTP ports `80` and `443` on the host, and making sure the container is placed within the `web` network we've created earlier on. Finally, we're giving this container a static name called `traefik`. @@ -199,7 +199,7 @@ Since the `traefik` container we've created and started earlier is also attached As mentioned earlier, we don't want containers exposed automatically by Træfik. The reason behind this is simple: we want to have control over this process ourselves. -Thanks to Docker labels, we can tell Træfik how to create it's internal routing configuration. +Thanks to Docker labels, we can tell Træfik how to create its internal routing configuration. Let's take a look at the labels themselves for the `app` service, which is a HTTP webservice listing on port 9000: @@ -222,7 +222,7 @@ We use both `container labels` and `service labels`. First, we specify the `backend` name which corresponds to the actual service we're routing **to**. We also tell Træfik to use the `web` network to route HTTP traffic to this container. -With the `traefik.enable` label, we tell Træfik to include this container in it's internal configuration. +With the `traefik.enable` label, we tell Træfik to include this container in its internal configuration. With the `frontend.rule` label, we tell Træfik that we want to route to this container if the incoming HTTP request contains the `Host` `app.my-awesome-app.org`. Essentially, this is the actual rule used for Layer-7 load balancing. diff --git a/integration/grpc_test.go b/integration/grpc_test.go index 24971a727..f7b8f0b36 100644 --- a/integration/grpc_test.go +++ b/integration/grpc_test.go @@ -1,6 +1,7 @@ package integration import ( + "context" "crypto/rand" "crypto/tls" "crypto/x509" @@ -13,7 +14,6 @@ import ( "github.com/containous/traefik/integration/helloworld" "github.com/containous/traefik/integration/try" "github.com/go-check/check" - "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) diff --git a/integration/helloworld/helloworld.pb.go b/integration/helloworld/helloworld.pb.go index c23df8f18..bfdd5f9dc 100644 --- a/integration/helloworld/helloworld.pb.go +++ b/integration/helloworld/helloworld.pb.go @@ -20,7 +20,8 @@ import fmt "fmt" import math "math" import ( - context "golang.org/x/net/context" + context "context" + grpc "google.golang.org/grpc" ) diff --git a/middlewares/redirect/redirect.go b/middlewares/redirect/redirect.go index 655709948..c1ec263b3 100644 --- a/middlewares/redirect/redirect.go +++ b/middlewares/redirect/redirect.go @@ -32,7 +32,7 @@ func NewEntryPointHandler(dstEntryPoint *configuration.EntryPoint, permanent boo protocol = "https" } - replacement := protocol + "://$1" + match[0] + "$2" + replacement := protocol + "://${1}" + match[0] + "${2}" return NewRegexHandler(defaultRedirectRegex, replacement, permanent) } diff --git a/provider/docker/swarm_test.go b/provider/docker/swarm_test.go index fe5c2f68a..68e6d972e 100644 --- a/provider/docker/swarm_test.go +++ b/provider/docker/swarm_test.go @@ -1,6 +1,7 @@ package docker import ( + "context" "strconv" "testing" "time" @@ -11,7 +12,6 @@ import ( "github.com/docker/docker/api/types/swarm" dockerclient "github.com/docker/docker/client" "github.com/stretchr/testify/assert" - "golang.org/x/net/context" ) type fakeTasksClient struct { diff --git a/server/server.go b/server/server.go index 0790be28f..458908262 100644 --- a/server/server.go +++ b/server/server.go @@ -486,6 +486,7 @@ func (s *serverEntryPoint) getCertificate(clientHello *tls.ClientHelloInfo) (*tl } } } + log.Debugf("No certificate provided dynamically can check the domain %q, a per default certificate will be used.", domainToCheck) } return nil, nil } @@ -904,7 +905,7 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura entryPoint := globalConfiguration.EntryPoints[entryPointName] n := negroni.New() - if entryPoint.Redirect != nil { + if entryPoint.Redirect != nil && entryPointName != entryPoint.Redirect.EntryPoint { if redirectHandlers[entryPointName] != nil { n.Use(redirectHandlers[entryPointName]) } else if handler, err := s.buildRedirectHandler(entryPointName, entryPoint.Redirect); err != nil { @@ -1102,7 +1103,7 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura log.Infof("Configured IP Whitelists: %s", frontend.WhitelistSourceRange) } - if frontend.Redirect != nil { + if frontend.Redirect != nil && entryPointName != frontend.Redirect.EntryPoint { rewrite, err := s.buildRedirectHandler(entryPointName, frontend.Redirect) if err != nil { log.Errorf("Error creating Frontend Redirect: %v", err) diff --git a/tls/certificate.go b/tls/certificate.go index c293bedc2..ccff7bc4f 100644 --- a/tls/certificate.go +++ b/tls/certificate.go @@ -101,7 +101,8 @@ func (c *Certificates) CreateTLSConfig(entryPointName string) (*tls.Config, map[ for _, certificate := range *c { err := certificate.AppendCertificates(domainsCertificates, entryPointName) if err != nil { - return nil, nil, err + log.Errorf("Unable to add a certificate to the entryPoint %q : %v", entryPointName, err) + continue } for _, certDom := range domainsCertificates { for _, cert := range certDom.Get().(map[string]*tls.Certificate) { @@ -133,16 +134,16 @@ func (c *Certificate) AppendCertificates(certs map[string]*DomainsCertificates, certContent, err := c.CertFile.Read() if err != nil { - return err + return fmt.Errorf("unable to read CertFile : %v", err) } keyContent, err := c.KeyFile.Read() if err != nil { - return err + return fmt.Errorf("uUnable to read KeyFile : %v", err) } tlsCert, err := tls.X509KeyPair(certContent, keyContent) if err != nil { - return err + return fmt.Errorf("unable to generate TLS certificate : %v", err) } parsedCert, _ := x509.ParseCertificate(tlsCert.Certificate[0]) diff --git a/vendor/github.com/containous/flaeg/flaeg.go b/vendor/github.com/containous/flaeg/flaeg.go index fb4512537..8b1421d31 100644 --- a/vendor/github.com/containous/flaeg/flaeg.go +++ b/vendor/github.com/containous/flaeg/flaeg.go @@ -12,64 +12,63 @@ import ( "strings" "text/tabwriter" "text/template" - "time" + "github.com/containous/flaeg/parse" flag "github.com/ogier/pflag" ) // ErrParserNotFound is thrown when a field is flaged but not parser match its type -var ErrParserNotFound = errors.New("Parser not found or custom parser missing") +var ErrParserNotFound = errors.New("parser not found or custom parser missing") -// GetTypesRecursive links in flagmap a flag with its reflect.StructField +// GetTypesRecursive links in flagMap a flag with its reflect.StructField // You can whether provide objValue on a structure or a pointer to structure as first argument -// Flags are genereted from field name or from StructTag -func getTypesRecursive(objValue reflect.Value, flagmap map[string]reflect.StructField, key string) error { +// Flags are generated from field name or from StructTag +func getTypesRecursive(objValue reflect.Value, flagMap map[string]reflect.StructField, key string) error { name := key switch objValue.Kind() { case reflect.Struct: - for i := 0; i < objValue.NumField(); i++ { if objValue.Type().Field(i).Anonymous { - if err := getTypesRecursive(objValue.Field(i), flagmap, name); err != nil { + if err := getTypesRecursive(objValue.Field(i), flagMap, name); err != nil { return err } } else if len(objValue.Type().Field(i).Tag.Get("description")) > 0 { fieldName := objValue.Type().Field(i).Name if !isExported(fieldName) { - return fmt.Errorf("Field %s is an unexported field", fieldName) + return fmt.Errorf("field %s is an unexported field", fieldName) } - name += objValue.Type().Name() if tag := objValue.Type().Field(i).Tag.Get("long"); len(tag) > 0 { fieldName = tag } + if len(key) == 0 { - //Lower Camel Case - //name = strings.ToLower(string(fieldName[0])) + fieldName[1:] name = strings.ToLower(fieldName) } else { name = key + "." + strings.ToLower(fieldName) } - if _, ok := flagmap[name]; ok { - return errors.New("Tag already exists: " + name) - } - flagmap[name] = objValue.Type().Field(i) - if err := getTypesRecursive(objValue.Field(i), flagmap, name); err != nil { + if _, ok := flagMap[name]; ok { + return fmt.Errorf("tag already exists: %s", name) + } + flagMap[name] = objValue.Type().Field(i) + + if err := getTypesRecursive(objValue.Field(i), flagMap, name); err != nil { return err } } - } case reflect.Ptr: if len(key) > 0 { - field := flagmap[name] + field := flagMap[name] field.Type = reflect.TypeOf(false) - flagmap[name] = field + flagMap[name] = field } + typ := objValue.Type().Elem() inst := reflect.New(typ).Elem() - if err := getTypesRecursive(inst, flagmap, name); err != nil { + + if err := getTypesRecursive(inst, flagMap, name); err != nil { return err } default: @@ -78,14 +77,15 @@ func getTypesRecursive(objValue reflect.Value, flagmap map[string]reflect.Struct return nil } -//GetPointerFlags returns flags on pointers +// GetBoolFlags returns flags on pointers func GetBoolFlags(config interface{}) ([]string, error) { - flagmap := make(map[string]reflect.StructField) - if err := getTypesRecursive(reflect.ValueOf(config), flagmap, ""); err != nil { + flagMap := make(map[string]reflect.StructField) + if err := getTypesRecursive(reflect.ValueOf(config), flagMap, ""); err != nil { return []string{}, err } - flags := make([]string, 0, len(flagmap)) - for f, structField := range flagmap { + + flags := make([]string, 0, len(flagMap)) + for f, structField := range flagMap { if structField.Type.Kind() == reflect.Bool { flags = append(flags, f) } @@ -93,86 +93,42 @@ func GetBoolFlags(config interface{}) ([]string, error) { return flags, nil } -//GetFlags returns flags +// GetFlags returns flags func GetFlags(config interface{}) ([]string, error) { - flagmap := make(map[string]reflect.StructField) - if err := getTypesRecursive(reflect.ValueOf(config), flagmap, ""); err != nil { + flagMap := make(map[string]reflect.StructField) + if err := getTypesRecursive(reflect.ValueOf(config), flagMap, ""); err != nil { return []string{}, err } - flags := make([]string, 0, len(flagmap)) - for f := range flagmap { + + flags := make([]string, 0, len(flagMap)) + for f := range flagMap { flags = append(flags, f) } return flags, nil } -//loadParsers loads default parsers and custom parsers given as parameter. Return a map [reflect.Type]parsers -// bool, int, int64, uint, uint64, float64, -func loadParsers(customParsers map[reflect.Type]Parser) (map[reflect.Type]Parser, error) { - parsers := map[reflect.Type]Parser{} - - var boolParser boolValue - parsers[reflect.TypeOf(true)] = &boolParser - - var intParser intValue - parsers[reflect.TypeOf(1)] = &intParser - - var int64Parser int64Value - parsers[reflect.TypeOf(int64(1))] = &int64Parser - - var uintParser uintValue - parsers[reflect.TypeOf(uint(1))] = &uintParser - - var uint64Parser uint64Value - parsers[reflect.TypeOf(uint64(1))] = &uint64Parser - - var stringParser stringValue - parsers[reflect.TypeOf("")] = &stringParser - - var float64Parser float64Value - parsers[reflect.TypeOf(float64(1.5))] = &float64Parser - - var durationParser Duration - parsers[reflect.TypeOf(Duration(time.Second))] = &durationParser - - var timeParser timeValue - parsers[reflect.TypeOf(time.Now())] = &timeParser - - for rType, parser := range customParsers { - parsers[rType] = parser - } - return parsers, nil -} - -//ParseArgs : parses args return valmap map[flag]Getter, using parsers map[type]Getter -//args must be formated as like as flag documentation. See https://golang.org/pkg/flag -func parseArgs(args []string, flagmap map[string]reflect.StructField, parsers map[reflect.Type]Parser) (map[string]Parser, error) { - //Return var - valmap := make(map[string]Parser) - //Visitor in flag.Parse - flagList := []*flag.Flag{} - visitor := func(fl *flag.Flag) { - flagList = append(flagList, fl) - } - newParsers := map[string]Parser{} +// 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{} flagSet := flag.NewFlagSet("flaeg.Load", flag.ContinueOnError) - //Disable output + + // Disable output flagSet.SetOutput(ioutil.Discard) + var err error - for flag, structField := range flagmap { - //for _, flag := range flags { - //structField := flagmap[flag] + for flg, structField := range flagMap { if parser, ok := parsers[structField.Type]; ok { - newparserValue := reflect.New(reflect.TypeOf(parser).Elem()) - newparserValue.Elem().Set(reflect.ValueOf(parser).Elem()) - newparser := newparserValue.Interface().(Parser) + newParserValue := reflect.New(reflect.TypeOf(parser).Elem()) + newParserValue.Elem().Set(reflect.ValueOf(parser).Elem()) + newParser := newParserValue.Interface().(parse.Parser) + if short := structField.Tag.Get("short"); len(short) == 1 { - // fmt.Printf("short : %s long : %s\n", short, flag) - flagSet.VarP(newparser, flag, short, structField.Tag.Get("description")) + flagSet.VarP(newParser, flg, short, structField.Tag.Get("description")) } else { - flagSet.Var(newparser, flag, structField.Tag.Get("description")) + flagSet.Var(newParser, flg, structField.Tag.Get("description")) } - newParsers[flag] = newparser + newParsers[flg] = newParser } else { err = ErrParserNotFound } @@ -180,24 +136,35 @@ func parseArgs(args []string, flagmap map[string]reflect.StructField, parsers ma // prevents case sensitivity issue args = argsToLower(args) - if err := flagSet.Parse(args); err != nil { - return nil, err + if errParse := flagSet.Parse(args); errParse != nil { + return nil, errParse } - //Fill flagList with parsed flags + // Visitor in flag.Parse + var flagList []*flag.Flag + visitor := func(fl *flag.Flag) { + flagList = append(flagList, fl) + } + + // Fill flagList with parsed flags flagSet.Visit(visitor) - //Return parsers on parsed flag - for _, flag := range flagList { - valmap[flag.Name] = newParsers[flag.Name] + + // Return var + valMap := make(map[string]parse.Parser) + + // Return parsers on parsed flag + for _, flg := range flagList { + valMap[flg.Name] = newParsers[flg.Name] } - return valmap, err + return valMap, err } func getDefaultValue(defaultValue reflect.Value, defaultPointersValue reflect.Value, defaultValmap map[string]reflect.Value, key string) error { if defaultValue.Type() != defaultPointersValue.Type() { - 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()) + 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()) } + name := key switch defaultValue.Kind() { case reflect.Struct: @@ -207,22 +174,19 @@ func getDefaultValue(defaultValue reflect.Value, defaultPointersValue reflect.Va return err } } else if len(defaultValue.Type().Field(i).Tag.Get("description")) > 0 { - name += defaultValue.Type().Name() fieldName := defaultValue.Type().Field(i).Name if tag := defaultValue.Type().Field(i).Tag.Get("long"); len(tag) > 0 { fieldName = tag } + if len(key) == 0 { name = strings.ToLower(fieldName) } else { name = key + "." + strings.ToLower(fieldName) } + if defaultValue.Field(i).Kind() != reflect.Ptr { - // if _, ok := defaultValmap[name]; ok { - // return errors.New("Tag already exists: " + name) - // } defaultValmap[name] = defaultValue.Field(i) - // fmt.Printf("%s: got default value %+v\n", name, defaultValue.Field(i)) } if err := getDefaultValue(defaultValue.Field(i), defaultPointersValue.Field(i), defaultValmap, name); err != nil { return err @@ -232,14 +196,14 @@ func getDefaultValue(defaultValue reflect.Value, defaultPointersValue reflect.Va case reflect.Ptr: if !defaultPointersValue.IsNil() { if len(key) != 0 { - //turn ptr fields to nil + // turn ptr fields to nil defaultPointersNilValue, err := setPointersNil(defaultPointersValue) if err != nil { return err } defaultValmap[name] = defaultPointersNilValue - // fmt.Printf("%s: got default value %+v\n", name, defaultPointersNilValue) } + if !defaultValue.IsNil() { if err := getDefaultValue(defaultValue.Elem(), defaultPointersValue.Elem(), defaultValmap, name); err != nil { return err @@ -253,8 +217,8 @@ func getDefaultValue(defaultValue reflect.Value, defaultPointersValue reflect.Va instValue := reflect.New(defaultPointersValue.Type().Elem()) if len(key) != 0 { defaultValmap[name] = instValue - // fmt.Printf("%s: got default value %+v\n", name, instValue) } + if !defaultValue.IsNil() { if err := getDefaultValue(defaultValue.Elem(), instValue.Elem(), defaultValmap, name); err != nil { return err @@ -271,17 +235,18 @@ func getDefaultValue(defaultValue reflect.Value, defaultPointersValue reflect.Va return nil } -//objValue a reflect.Value of a not-nil pointer on a struct +// objValue a reflect.Value of a not-nil pointer on a struct func setPointersNil(objValue reflect.Value) (reflect.Value, error) { if objValue.Kind() != reflect.Ptr { - return objValue, fmt.Errorf("Parameters objValue must be a not-nil pointer on a struct, not a %s", objValue.Kind().String()) + return objValue, fmt.Errorf("parameters objValue must be a not-nil pointer on a struct, not a %s", objValue.Kind()) } else if objValue.IsNil() { - return objValue, fmt.Errorf("Parameters objValue must be a not-nil pointer") + return objValue, errors.New("parameters objValue must be a not-nil pointer") } 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 } - //Clone + + // Clone starObjValue := objValue.Elem() nilPointersObjVal := reflect.New(starObjValue.Type()) starNilPointersObjVal := nilPointersObjVal.Elem() @@ -295,39 +260,38 @@ func setPointersNil(objValue reflect.Value) (reflect.Value, error) { return nilPointersObjVal, nil } -//FillStructRecursive initialize a value of any taged Struct given by reference -func fillStructRecursive(objValue reflect.Value, defaultPointerValmap map[string]reflect.Value, valmap map[string]Parser, key string) error { +// 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 { name := key switch objValue.Kind() { case reflect.Struct: for i := 0; i < objValue.Type().NumField(); i++ { if objValue.Type().Field(i).Anonymous { - if err := fillStructRecursive(objValue.Field(i), defaultPointerValmap, valmap, name); err != nil { + if err := fillStructRecursive(objValue.Field(i), defaultPointerValMap, valMap, name); err != nil { return err } } else if len(objValue.Type().Field(i).Tag.Get("description")) > 0 { - name += objValue.Type().Name() fieldName := objValue.Type().Field(i).Name if tag := objValue.Type().Field(i).Tag.Get("long"); len(tag) > 0 { fieldName = tag } + if len(key) == 0 { name = strings.ToLower(fieldName) } else { name = key + "." + strings.ToLower(fieldName) } - // fmt.Println(name) - if objValue.Field(i).Kind() != reflect.Ptr { - if val, ok := valmap[name]; ok { - // fmt.Printf("%s : set def val\n", name) + if objValue.Field(i).Kind() != reflect.Ptr { + if val, ok := valMap[name]; ok { if err := setFields(objValue.Field(i), val); err != nil { return err } } } - if err := fillStructRecursive(objValue.Field(i), defaultPointerValmap, valmap, name); err != nil { + + if err := fillStructRecursive(objValue.Field(i), defaultPointerValMap, valMap, name); err != nil { return err } } @@ -335,39 +299,38 @@ func fillStructRecursive(objValue reflect.Value, defaultPointerValmap map[string case reflect.Ptr: if len(key) == 0 && !objValue.IsNil() { - if err := fillStructRecursive(objValue.Elem(), defaultPointerValmap, valmap, name); err != nil { - return err - } - return nil + return fillStructRecursive(objValue.Elem(), defaultPointerValMap, valMap, name) } + contains := false - for flag := range valmap { + for flg := range valMap { // TODO replace by regexp - if strings.HasPrefix(flag, name+".") { + if strings.HasPrefix(flg, name+".") { contains = true break } } + needDefault := false - if _, ok := valmap[name]; ok { - needDefault = valmap[name].Get().(bool) + if _, ok := valMap[name]; ok { + needDefault = valMap[name].Get().(bool) } if contains && objValue.IsNil() { needDefault = true } if needDefault { - if defVal, ok := defaultPointerValmap[name]; ok { - //set default pointer value - // fmt.Printf("%s : set default value %+v\n", name, defVal) + if defVal, ok := defaultPointerValMap[name]; ok { + // set default pointer value objValue.Set(defVal) } else { return fmt.Errorf("flag %s default value not provided", name) } } + if !objValue.IsNil() && contains { if objValue.Type().Elem().Kind() == reflect.Struct { - if err := fillStructRecursive(objValue.Elem(), defaultPointerValmap, valmap, name); err != nil { + if err := fillStructRecursive(objValue.Elem(), defaultPointerValMap, valMap, name); err != nil { return err } } @@ -378,35 +341,35 @@ func fillStructRecursive(objValue reflect.Value, defaultPointerValmap map[string return nil } -// SetFields sets value to fieldValue using tag as key in valmap -func setFields(fieldValue reflect.Value, val Parser) error { +// SetFields sets value to fieldValue using tag as key in valMap +func setFields(fieldValue reflect.Value, val parse.Parser) error { if fieldValue.CanSet() { fieldValue.Set(reflect.ValueOf(val).Elem().Convert(fieldValue.Type())) } else { - return errors.New(fieldValue.Type().String() + " is not settable.") + return fmt.Errorf("%s is not settable", fieldValue.Type().String()) } return nil } -//PrintHelp generates and prints command line help -func PrintHelp(flagmap map[string]reflect.StructField, defaultValmap map[string]reflect.Value, parsers map[reflect.Type]Parser) error { - return PrintHelpWithCommand(flagmap, defaultValmap, parsers, nil, nil) +// 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) } -//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]Parser) error { +// 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 { if err != flag.ErrHelp { - fmt.Printf("Error : %s\n", err) + fmt.Printf("Error: %s\n", err) } if !strings.Contains(err.Error(), ":No parser for type") { - PrintHelp(flagmap, defaultValmap, parsers) + PrintHelp(flagMap, defaultValmap, parsers) } return err } -//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]Parser) error { +// 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 { cmd := &Command{ Config: config, DefaultPointersConfig: defaultValue, @@ -415,8 +378,8 @@ func LoadWithParsers(config interface{}, defaultValue interface{}, args []string return LoadWithCommand(cmd, args, customParsers, nil) } -//Load initializes config : struct fields given by reference, with args : arguments. -//Some custom parsers may be given. +// Load initializes config : struct fields given by reference, with args : arguments. +// Some custom parsers may be given. func Load(config interface{}, defaultValue interface{}, args []string) error { return LoadWithParsers(config, defaultValue, args, nil) } @@ -430,35 +393,34 @@ type Command struct { Name string Description string Config interface{} - DefaultPointersConfig interface{} //TODO:case DefaultPointersConfig is nil + DefaultPointersConfig interface{} // TODO: case DefaultPointersConfig is nil Run func() error Metadata map[string]string } -//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]Parser, subCommand []*Command) error { - - parsers, err := loadParsers(customParsers) +// 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) if err != nil { return err } - tagsmap := make(map[string]reflect.StructField) - if err := getTypesRecursive(reflect.ValueOf(cmd.Config), tagsmap, ""); err != nil { + tagsMap := make(map[string]reflect.StructField) + if err := getTypesRecursive(reflect.ValueOf(cmd.Config), tagsMap, ""); err != nil { return err } - defaultValmap := make(map[string]reflect.Value) - if err := getDefaultValue(reflect.ValueOf(cmd.Config), reflect.ValueOf(cmd.DefaultPointersConfig), defaultValmap, ""); err != nil { + defaultValMap := make(map[string]reflect.Value) + if err := getDefaultValue(reflect.ValueOf(cmd.Config), reflect.ValueOf(cmd.DefaultPointersConfig), defaultValMap, ""); err != nil { return err } - valmap, errParseArgs := parseArgs(cmdArgs, tagsmap, parsers) + valMap, errParseArgs := parseArgs(cmdArgs, tagsMap, parsers) if errParseArgs != nil && errParseArgs != ErrParserNotFound { - return PrintErrorWithCommand(errParseArgs, tagsmap, defaultValmap, parsers, cmd, subCommand) + return PrintErrorWithCommand(errParseArgs, tagsMap, defaultValMap, parsers, cmd, subCommand) } - if err := fillStructRecursive(reflect.ValueOf(cmd.Config), defaultValmap, valmap, ""); err != nil { + if err := fillStructRecursive(reflect.ValueOf(cmd.Config), defaultValMap, valMap, ""); err != nil { return err } @@ -469,8 +431,8 @@ func LoadWithCommand(cmd *Command, cmdArgs []string, customParsers map[reflect.T return nil } -//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]Parser, cmd *Command, subCmd []*Command) error { +// 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 { // Define a templates // Using POSXE STD : http://pubs.opengroup.org/onlinepubs/9699919799/ const helper = `{{if .ProgDescription}}{{.ProgDescription}} @@ -504,7 +466,7 @@ Flags: _, tempStruct.ProgName = path.Split(os.Args[0]) } - //Run Template + // Run Template tmplHelper, err := template.New("helper").Parse(helper) if err != nil { return err @@ -514,38 +476,38 @@ Flags: return err } - return printFlagsDescriptionsDefaultValues(flagmap, defaultValmap, parsers, os.Stdout) + return printFlagsDescriptionsDefaultValues(flagMap, defaultValMap, parsers, os.Stdout) } -func printFlagsDescriptionsDefaultValues(flagmap map[string]reflect.StructField, defaultValmap map[string]reflect.Value, parsers map[reflect.Type]Parser, output io.Writer) error { +func printFlagsDescriptionsDefaultValues(flagMap map[string]reflect.StructField, defaultValMap map[string]reflect.Value, parsers map[reflect.Type]parse.Parser, output io.Writer) error { // Sort alphabetically & Delete unparsable flags in a slice - flags := []string{} - for flag, field := range flagmap { + var flags []string + for flg, field := range flagMap { if _, ok := parsers[field.Type]; ok { - flags = append(flags, flag) + flags = append(flags, flg) } } sort.Strings(flags) // Process data - descriptions := []string{} - defaultValues := []string{} - flagsWithDashs := []string{} - shortFlagsWithDash := []string{} - for _, flag := range flags { - field := flagmap[flag] + var descriptions []string + var defaultValues []string + var flagsWithDash []string + var shortFlagsWithDash []string + for _, flg := range flags { + field := flagMap[flg] if short := field.Tag.Get("short"); len(short) == 1 { shortFlagsWithDash = append(shortFlagsWithDash, "-"+short+",") } else { shortFlagsWithDash = append(shortFlagsWithDash, "") } - flagsWithDashs = append(flagsWithDashs, "--"+flag) + flagsWithDash = append(flagsWithDash, "--"+flg) - //flag on pointer ? - if defVal, ok := defaultValmap[flag]; ok { + // flag on pointer ? + if defVal, ok := defaultValMap[flg]; ok { if defVal.Kind() != reflect.Ptr { // Set defaultValue on parsers - parsers[field.Type].SetValue(defaultValmap[flag].Interface()) + parsers[field.Type].SetValue(defaultValMap[flg].Interface()) } if defVal := parsers[field.Type].String(); len(defVal) > 0 { @@ -560,17 +522,19 @@ func printFlagsDescriptionsDefaultValues(flagmap map[string]reflect.StructField, descriptions = append(descriptions, description) if i != 0 { defaultValues = append(defaultValues, "") - flagsWithDashs = append(flagsWithDashs, "") + flagsWithDash = append(flagsWithDash, "") shortFlagsWithDash = append(shortFlagsWithDash, "") } } } + //add help flag shortFlagsWithDash = append(shortFlagsWithDash, "-h,") - flagsWithDashs = append(flagsWithDashs, "--help") + flagsWithDash = append(flagsWithDash, "--help") descriptions = append(descriptions, "Print Help (this message) and exit") defaultValues = append(defaultValues, "") - return displayTab(output, shortFlagsWithDash, flagsWithDashs, descriptions, defaultValues) + + return displayTab(output, shortFlagsWithDash, flagsWithDash, descriptions, defaultValues) } func split(str string, width int) []string { if len(str) > width { @@ -578,16 +542,19 @@ func split(str string, width int) []string { if index == -1 { index = width } + return append([]string{strings.TrimSpace(str[:index])}, split(strings.TrimSpace(str[index:]), width)...) } return []string{str} } func displayTab(output io.Writer, columns ...[]string) error { - nbRow := len(columns[0]) - nbCol := len(columns) w := new(tabwriter.Writer) w.Init(output, 0, 4, 1, ' ', 0) + + nbRow := len(columns[0]) + nbCol := len(columns) + for i := 0; i < nbRow; i++ { row := "" for j, col := range columns { @@ -598,56 +565,58 @@ func displayTab(output io.Writer, columns ...[]string) error { } fmt.Fprintln(w, row) } - w.Flush() - return nil + + return w.Flush() } -//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]Parser, cmd *Command, subCmd []*Command) error { +// 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 { if err != flag.ErrHelp { fmt.Printf("Error here : %s\n", err) } - PrintHelpWithCommand(flagmap, defaultValmap, parsers, cmd, subCmd) + + PrintHelpWithCommand(flagMap, defaultValMap, parsers, cmd, subCmd) return err } -//Flaeg struct contains commands (at least the root one) -//and row arguments (command and/or flags) -//a map of custom parsers could be use +// Flaeg struct contains commands (at least the root one) +// and row arguments (command and/or flags) +// a map of custom parsers could be use type Flaeg struct { calledCommand *Command commands []*Command ///rootCommand is th fist one in this slice args []string - commmandArgs []string - customParsers map[reflect.Type]Parser + commandArgs []string + customParsers map[reflect.Type]parse.Parser } -//New creats and initialize a pointer on Flaeg +// New creates and initialize a pointer on Flaeg func New(rootCommand *Command, args []string) *Flaeg { var f Flaeg f.commands = []*Command{rootCommand} f.args = args - f.customParsers = map[reflect.Type]Parser{} + f.customParsers = map[reflect.Type]parse.Parser{} return &f } -//AddCommand adds sub-command to the root command +// AddCommand adds sub-command to the root command func (f *Flaeg) AddCommand(command *Command) { f.commands = append(f.commands, command) } -//AddParser adds custom parser for a type to the map of custom parsers -func (f *Flaeg) AddParser(typ reflect.Type, parser Parser) { +// AddParser adds custom parser for a type to the map of custom parsers +func (f *Flaeg) AddParser(typ reflect.Type, parser parse.Parser) { f.customParsers[typ] = parser } -// Run calls the command with flags given as agruments +// Run calls the command with flags given as arguments func (f *Flaeg) Run() error { if f.calledCommand == nil { if _, _, err := f.findCommandWithCommandArgs(); err != nil { return err } } + if _, err := f.Parse(f.calledCommand); err != nil { return err } @@ -658,15 +627,16 @@ func (f *Flaeg) Run() error { // It returns nil and a not nil error if it fails func (f *Flaeg) Parse(cmd *Command) (*Command, error) { if f.calledCommand == nil { - f.commmandArgs = f.args + f.commandArgs = f.args } - if err := LoadWithCommand(cmd, f.commmandArgs, f.customParsers, f.commands); err != nil { + + if err := LoadWithCommand(cmd, f.commandArgs, f.customParsers, f.commands); err != nil { return cmd, err } return cmd, nil } -//splitArgs takes args (type []string) and return command ("" if rootCommand) and command's args +// splitArgs takes args (type []string) and return command ("" if rootCommand) and command's args func splitArgs(args []string) (string, []string) { if len(args) >= 1 && len(args[0]) >= 1 && string(args[0][0]) != "-" { if len(args) == 1 { @@ -680,20 +650,20 @@ func splitArgs(args []string) (string, []string) { // 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) { - commandName := "" - commandName, f.commmandArgs = splitArgs(f.args) + var commandName string + commandName, f.commandArgs = splitArgs(f.args) if len(commandName) > 0 { for _, command := range f.commands { if commandName == command.Name { f.calledCommand = command - return f.calledCommand, f.commmandArgs, nil + return f.calledCommand, f.commandArgs, nil } } - return nil, []string{}, fmt.Errorf("Command %s not found", commandName) + return nil, []string{}, fmt.Errorf("command %s not found", commandName) } f.calledCommand = f.commands[0] - return f.calledCommand, f.commmandArgs, nil + return f.calledCommand, f.commandArgs, nil } // GetCommand splits args and returns the called command (by reference) @@ -706,15 +676,17 @@ func (f *Flaeg) GetCommand() (*Command, error) { return f.calledCommand, nil } -//isExported return true is the field (from fieldName) is exported, -//else false +// isExported return true is the field (from fieldName) is exported, +// else false func isExported(fieldName string) bool { if len(fieldName) < 1 { return false } + if string(fieldName[0]) == strings.ToUpper(string(fieldName[0])) { return true } + return false } @@ -722,22 +694,24 @@ func argToLower(inArg string) string { if len(inArg) < 2 { return strings.ToLower(inArg) } + var outArg string dashIndex := strings.Index(inArg, "--") if dashIndex == -1 { if dashIndex = strings.Index(inArg, "-"); dashIndex == -1 { return inArg } - //-fValue + // -fValue outArg = strings.ToLower(inArg[dashIndex:dashIndex+2]) + inArg[dashIndex+2:] return outArg } - //--flag + + // --flag if equalIndex := strings.Index(inArg, "="); equalIndex != -1 { - //--flag=value + // --flag=value outArg = strings.ToLower(inArg[dashIndex:equalIndex]) + inArg[equalIndex:] } else { - //--boolflag + // --boolflag outArg = strings.ToLower(inArg[dashIndex:]) } @@ -745,7 +719,7 @@ func argToLower(inArg string) string { } func argsToLower(inArgs []string) []string { - outArgs := make([]string, len(inArgs), len(inArgs)) + outArgs := make([]string, len(inArgs)) for i, inArg := range inArgs { outArgs[i] = argToLower(inArg) } diff --git a/vendor/github.com/containous/flaeg/flaeg_types.go b/vendor/github.com/containous/flaeg/flaeg_types.go new file mode 100644 index 000000000..54ee8f600 --- /dev/null +++ b/vendor/github.com/containous/flaeg/flaeg_types.go @@ -0,0 +1,7 @@ +package flaeg + +import "github.com/containous/flaeg/parse" + +// Duration is deprecated use parse.Duration instead +// Deprecated +type Duration = parse.Duration diff --git a/vendor/github.com/containous/flaeg/parse/parse.go b/vendor/github.com/containous/flaeg/parse/parse.go new file mode 100644 index 000000000..4db67b10d --- /dev/null +++ b/vendor/github.com/containous/flaeg/parse/parse.go @@ -0,0 +1,301 @@ +package parse + +import ( + "flag" + "fmt" + "reflect" + "strconv" + "strings" + "time" +) + +// Parser is an interface that allows the contents of a flag.Getter to be set. +type Parser interface { + flag.Getter + SetValue(interface{}) +} + +// BoolValue bool Value type +type BoolValue bool + +// Set sets bool value from the given string value. +func (b *BoolValue) Set(s string) error { + v, err := strconv.ParseBool(s) + *b = BoolValue(v) + return err +} + +// Get returns the bool value. +func (b *BoolValue) Get() interface{} { return bool(*b) } + +func (b *BoolValue) String() string { return fmt.Sprintf("%v", *b) } + +// IsBoolFlag return true +func (b *BoolValue) IsBoolFlag() bool { return true } + +// SetValue sets the duration from the given bool-asserted value. +func (b *BoolValue) SetValue(val interface{}) { + *b = BoolValue(val.(bool)) +} + +// BoolFlag optional interface to indicate boolean flags that can be +// supplied without "=value" text +type BoolFlag interface { + flag.Value + IsBoolFlag() bool +} + +// IntValue int Value +type IntValue int + +// Set sets int value from the given string value. +func (i *IntValue) Set(s string) error { + v, err := strconv.ParseInt(s, 0, 64) + *i = IntValue(v) + return err +} + +// Get returns the int value. +func (i *IntValue) Get() interface{} { return int(*i) } + +func (i *IntValue) String() string { return fmt.Sprintf("%v", *i) } + +// SetValue sets the IntValue from the given int-asserted value. +func (i *IntValue) SetValue(val interface{}) { + *i = IntValue(val.(int)) +} + +// Int64Value int64 Value +type Int64Value int64 + +// Set sets int64 value from the given string value. +func (i *Int64Value) Set(s string) error { + v, err := strconv.ParseInt(s, 0, 64) + *i = Int64Value(v) + return err +} + +// Get returns the int64 value. +func (i *Int64Value) Get() interface{} { return int64(*i) } + +func (i *Int64Value) String() string { return fmt.Sprintf("%v", *i) } + +// SetValue sets the Int64Value from the given int64-asserted value. +func (i *Int64Value) SetValue(val interface{}) { + *i = Int64Value(val.(int64)) +} + +// UintValue uint Value +type UintValue uint + +// Set sets uint value from the given string value. +func (i *UintValue) Set(s string) error { + v, err := strconv.ParseUint(s, 0, 64) + *i = UintValue(v) + return err +} + +// Get returns the uint value. +func (i *UintValue) Get() interface{} { return uint(*i) } + +func (i *UintValue) String() string { return fmt.Sprintf("%v", *i) } + +// SetValue sets the UintValue from the given uint-asserted value. +func (i *UintValue) SetValue(val interface{}) { + *i = UintValue(val.(uint)) +} + +// Uint64Value uint64 Value +type Uint64Value uint64 + +// Set sets uint64 value from the given string value. +func (i *Uint64Value) Set(s string) error { + v, err := strconv.ParseUint(s, 0, 64) + *i = Uint64Value(v) + return err +} + +// Get returns the uint64 value. +func (i *Uint64Value) Get() interface{} { return uint64(*i) } + +func (i *Uint64Value) String() string { return fmt.Sprintf("%v", *i) } + +// SetValue sets the Uint64Value from the given uint64-asserted value. +func (i *Uint64Value) SetValue(val interface{}) { + *i = Uint64Value(val.(uint64)) +} + +// StringValue string Value +type StringValue string + +// Set sets string value from the given string value. +func (s *StringValue) Set(val string) error { + *s = StringValue(val) + return nil +} + +// Get returns the string value. +func (s *StringValue) Get() interface{} { return string(*s) } + +func (s *StringValue) String() string { return string(*s) } + +// SetValue sets the StringValue from the given string-asserted value. +func (s *StringValue) SetValue(val interface{}) { + *s = StringValue(val.(string)) +} + +// Float64Value float64 Value +type Float64Value float64 + +// Set sets float64 value from the given string value. +func (f *Float64Value) Set(s string) error { + v, err := strconv.ParseFloat(s, 64) + *f = Float64Value(v) + return err +} + +// Get returns the float64 value. +func (f *Float64Value) Get() interface{} { return float64(*f) } + +func (f *Float64Value) String() string { return fmt.Sprintf("%v", *f) } + +// SetValue sets the Float64Value from the given float64-asserted value. +func (f *Float64Value) SetValue(val interface{}) { + *f = Float64Value(val.(float64)) +} + +// Duration is a custom type suitable for parsing duration values. +// It supports `time.ParseDuration`-compatible values and suffix-less digits; in +// the latter case, seconds are assumed. +type Duration time.Duration + +// Set sets the duration from the given string value. +func (d *Duration) Set(s string) error { + if v, err := strconv.Atoi(s); err == nil { + *d = Duration(time.Duration(v) * time.Second) + return nil + } + + v, err := time.ParseDuration(s) + *d = Duration(v) + return err +} + +// Get returns the duration value. +func (d *Duration) Get() interface{} { return time.Duration(*d) } + +// String returns a string representation of the duration value. +func (d *Duration) String() string { return (*time.Duration)(d).String() } + +// SetValue sets the duration from the given Duration-asserted value. +func (d *Duration) SetValue(val interface{}) { + *d = val.(Duration) +} + +// MarshalText serialize the given duration value into a text. +func (d *Duration) MarshalText() ([]byte, error) { + return []byte(d.String()), nil +} + +// UnmarshalText deserializes the given text into a duration value. +// It is meant to support TOML decoding of durations. +func (d *Duration) UnmarshalText(text []byte) error { + return d.Set(string(text)) +} + +// UnmarshalJSON deserializes the given text into a duration value. +func (d *Duration) UnmarshalJSON(text []byte) error { + if v, err := strconv.Atoi(string(text)); err == nil { + *d = Duration(time.Duration(v)) + return nil + } + + v, err := time.ParseDuration(string(text)) + *d = Duration(v) + return err +} + +// TimeValue time.Time Value +type TimeValue time.Time + +// Set sets time.Time value from the given string value. +func (t *TimeValue) Set(s string) error { + v, err := time.Parse(time.RFC3339, s) + *t = TimeValue(v) + return err +} + +// Get returns the time.Time value. +func (t *TimeValue) Get() interface{} { return time.Time(*t) } + +func (t *TimeValue) String() string { return (*time.Time)(t).String() } + +// SetValue sets the TimeValue from the given time.Time-asserted value. +func (t *TimeValue) SetValue(val interface{}) { + *t = TimeValue(val.(time.Time)) +} + +// SliceStrings parse slice of strings +type SliceStrings []string + +// Set adds strings elem into the the parser. +// It splits str on , and ; +func (s *SliceStrings) Set(str string) error { + fargs := func(c rune) bool { + return c == ',' || c == ';' + } + // get function + slice := strings.FieldsFunc(str, fargs) + *s = append(*s, slice...) + return nil +} + +// Get []string +func (s *SliceStrings) Get() interface{} { return []string(*s) } + +// String return slice in a string +func (s *SliceStrings) String() string { return fmt.Sprintf("%v", *s) } + +// SetValue sets []string into the parser +func (s *SliceStrings) SetValue(val interface{}) { + *s = SliceStrings(val.([]string)) +} + +// LoadParsers loads default parsers and custom parsers given as parameter. +// Return a map [reflect.Type]parsers +// bool, int, int64, uint, uint64, float64, +func LoadParsers(customParsers map[reflect.Type]Parser) (map[reflect.Type]Parser, error) { + parsers := map[reflect.Type]Parser{} + + var boolParser BoolValue + parsers[reflect.TypeOf(true)] = &boolParser + + var intParser IntValue + parsers[reflect.TypeOf(1)] = &intParser + + var int64Parser Int64Value + parsers[reflect.TypeOf(int64(1))] = &int64Parser + + var uintParser UintValue + parsers[reflect.TypeOf(uint(1))] = &uintParser + + var uint64Parser Uint64Value + parsers[reflect.TypeOf(uint64(1))] = &uint64Parser + + var stringParser StringValue + parsers[reflect.TypeOf("")] = &stringParser + + var float64Parser Float64Value + parsers[reflect.TypeOf(float64(1.5))] = &float64Parser + + var durationParser Duration + parsers[reflect.TypeOf(Duration(time.Second))] = &durationParser + + var timeParser TimeValue + parsers[reflect.TypeOf(time.Now())] = &timeParser + + for rType, parser := range customParsers { + parsers[rType] = parser + } + return parsers, nil +} diff --git a/vendor/github.com/containous/flaeg/parsers.go b/vendor/github.com/containous/flaeg/parsers.go deleted file mode 100644 index 7b99d2d2c..000000000 --- a/vendor/github.com/containous/flaeg/parsers.go +++ /dev/null @@ -1,221 +0,0 @@ -package flaeg - -import ( - "flag" - "fmt" - "strconv" - "strings" - "time" -) - -//TODO : add parsers on all types in https://golang.org/pkg/builtin/ - -// Parser is an interface that allows the contents of a flag.Getter to be set. -type Parser interface { - flag.Getter - SetValue(interface{}) -} - -// -- bool Value -type boolValue bool - -func (b *boolValue) Set(s string) error { - v, err := strconv.ParseBool(s) - *b = boolValue(v) - return err -} - -func (b *boolValue) Get() interface{} { return bool(*b) } - -func (b *boolValue) String() string { return fmt.Sprintf("%v", *b) } - -func (b *boolValue) IsBoolFlag() bool { return true } - -func (b *boolValue) SetValue(val interface{}) { - *b = boolValue(val.(bool)) -} - -// optional interface to indicate boolean flags that can be -// supplied without "=value" text -type boolFlag interface { - flag.Value - IsBoolFlag() bool -} - -// -- int Value -type intValue int - -func (i *intValue) Set(s string) error { - v, err := strconv.ParseInt(s, 0, 64) - *i = intValue(v) - return err -} - -func (i *intValue) Get() interface{} { return int(*i) } - -func (i *intValue) String() string { return fmt.Sprintf("%v", *i) } - -func (i *intValue) SetValue(val interface{}) { - *i = intValue(val.(int)) -} - -// -- int64 Value -type int64Value int64 - -func (i *int64Value) Set(s string) error { - v, err := strconv.ParseInt(s, 0, 64) - *i = int64Value(v) - return err -} - -func (i *int64Value) Get() interface{} { return int64(*i) } - -func (i *int64Value) String() string { return fmt.Sprintf("%v", *i) } - -func (i *int64Value) SetValue(val interface{}) { - *i = int64Value(val.(int64)) -} - -// -- uint Value -type uintValue uint - -func (i *uintValue) Set(s string) error { - v, err := strconv.ParseUint(s, 0, 64) - *i = uintValue(v) - return err -} - -func (i *uintValue) Get() interface{} { return uint(*i) } - -func (i *uintValue) String() string { return fmt.Sprintf("%v", *i) } - -func (i *uintValue) SetValue(val interface{}) { - *i = uintValue(val.(uint)) -} - -// -- uint64 Value -type uint64Value uint64 - -func (i *uint64Value) Set(s string) error { - v, err := strconv.ParseUint(s, 0, 64) - *i = uint64Value(v) - return err -} - -func (i *uint64Value) Get() interface{} { return uint64(*i) } - -func (i *uint64Value) String() string { return fmt.Sprintf("%v", *i) } - -func (i *uint64Value) SetValue(val interface{}) { - *i = uint64Value(val.(uint64)) -} - -// -- string Value -type stringValue string - -func (s *stringValue) Set(val string) error { - *s = stringValue(val) - return nil -} - -func (s *stringValue) Get() interface{} { return string(*s) } - -func (s *stringValue) String() string { return fmt.Sprintf("%s", *s) } - -func (s *stringValue) SetValue(val interface{}) { - *s = stringValue(val.(string)) -} - -// -- float64 Value -type float64Value float64 - -func (f *float64Value) Set(s string) error { - v, err := strconv.ParseFloat(s, 64) - *f = float64Value(v) - return err -} - -func (f *float64Value) Get() interface{} { return float64(*f) } - -func (f *float64Value) String() string { return fmt.Sprintf("%v", *f) } - -func (f *float64Value) SetValue(val interface{}) { - *f = float64Value(val.(float64)) -} - -// Duration is a custom type suitable for parsing duration values. -// It supports `time.ParseDuration`-compatible values and suffix-less digits; in -// the latter case, seconds are assumed. -type Duration time.Duration - -// Set sets the duration from the given string value. -func (d *Duration) Set(s string) error { - if v, err := strconv.Atoi(s); err == nil { - *d = Duration(time.Duration(v) * time.Second) - return nil - } - - v, err := time.ParseDuration(s) - *d = Duration(v) - return err -} - -// Get returns the duration value. -func (d *Duration) Get() interface{} { return time.Duration(*d) } - -// String returns a string representation of the duration value. -func (d *Duration) String() string { return (*time.Duration)(d).String() } - -// SetValue sets the duration from the given Duration-asserted value. -func (d *Duration) SetValue(val interface{}) { - *d = Duration(val.(Duration)) -} - -// UnmarshalText deserializes the given text into a duration value. -// It is meant to support TOML decoding of durations. -func (d *Duration) UnmarshalText(text []byte) error { - return d.Set(string(text)) -} - -// -- time.Time Value -type timeValue time.Time - -func (t *timeValue) Set(s string) error { - v, err := time.Parse(time.RFC3339, s) - *t = timeValue(v) - return err -} - -func (t *timeValue) Get() interface{} { return time.Time(*t) } - -func (t *timeValue) String() string { return (*time.Time)(t).String() } - -func (t *timeValue) SetValue(val interface{}) { - *t = timeValue(val.(time.Time)) -} - -//SliceStrings parse slice of strings -type SliceStrings []string - -//Set adds strings elem into the the parser -//it splits str on , and ; -func (s *SliceStrings) Set(str string) error { - fargs := func(c rune) bool { - return c == ',' || c == ';' - } - // get function - slice := strings.FieldsFunc(str, fargs) - *s = append(*s, slice...) - return nil -} - -//Get []string -func (s *SliceStrings) Get() interface{} { return []string(*s) } - -//String return slice in a string -func (s *SliceStrings) String() string { return fmt.Sprintf("%v", *s) } - -//SetValue sets []string into the parser -func (s *SliceStrings) SetValue(val interface{}) { - *s = SliceStrings(val.([]string)) -}