Merge branch 'v1.5' into master

This commit is contained in:
Fernandez Ludovic 2018-02-27 14:07:07 +01:00
commit a2db3e0499
18 changed files with 684 additions and 486 deletions

View file

@ -1,5 +1,27 @@
# Change Log # 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) ## [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) [All Commits](https://github.com/containous/traefik/compare/v1.5.1...v1.5.2)

9
Gopkg.lock generated
View file

@ -225,9 +225,12 @@
[[projects]] [[projects]]
name = "github.com/containous/flaeg" name = "github.com/containous/flaeg"
packages = ["."] packages = [
revision = "60c87a513a955ca7225e1b1c772581cea8420cb4" ".",
version = "v1.0.1" "parse"
]
revision = "963366c29a7acc2d6e02f4f9bcf260d5a1cf4968"
version = "v1.1.1"
[[projects]] [[projects]]
branch = "master" branch = "master"

View file

@ -222,6 +222,24 @@ func (dc *DomainsCertificates) exists(domainToFind Domain) (*DomainsCertificate,
return nil, false 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 // DomainsCertificate contains a certificate for multiple domains
type DomainsCertificate struct { type DomainsCertificate struct {
Domains Domain Domains Domain

View file

@ -391,7 +391,7 @@ func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificat
domain := types.CanonicalDomain(clientHello.ServerName) domain := types.CanonicalDomain(clientHello.ServerName)
account := a.store.Get().(*Account) account := a.store.Get().(*Account)
if providedCertificate := a.getProvidedCertificate([]string{domain}); providedCertificate != nil { if providedCertificate := a.getProvidedCertificate(domain); providedCertificate != nil {
return providedCertificate, nil return providedCertificate, nil
} }
@ -625,11 +625,6 @@ func (a *ACME) LoadCertificateForDomains(domains []string) {
domains = fun.Map(types.CanonicalDomain, domains).([]string) domains = fun.Map(types.CanonicalDomain, domains).([]string)
// Check provided certificates
if a.getProvidedCertificate(domains) != nil {
return
}
operation := func() error { operation := func() error {
if a.client == nil { if a.client == nil {
return errors.New("ACME client still not built") return errors.New("ACME client still not built")
@ -647,32 +642,34 @@ func (a *ACME) LoadCertificateForDomains(domains []string) {
return return
} }
account := a.store.Get().(*Account) account := a.store.Get().(*Account)
var domain Domain
if len(domains) > 1 { // Check provided certificates
domain = Domain{Main: domains[0], SANs: domains[1:]} uncheckedDomains := a.getUncheckedDomains(domains, account)
} else { if len(uncheckedDomains) == 0 {
domain = Domain{Main: domains[0]}
}
if _, exists := account.DomainsCertificate.exists(domain); exists {
// domain already exists
return return
} }
certificate, err := a.getDomainsCertificates(domains) certificate, err := a.getDomainsCertificates(uncheckedDomains)
if err != nil { if err != nil {
log.Errorf("Error getting ACME certificates %+v : %v", domains, err) log.Errorf("Error getting ACME certificates %+v : %v", uncheckedDomains, err)
return return
} }
log.Debugf("Got certificate for domains %+v", domains) log.Debugf("Got certificate for domains %+v", uncheckedDomains)
transaction, object, err := a.store.Begin() transaction, object, err := a.store.Begin()
if err != nil { if err != nil {
log.Errorf("Error creating transaction %+v : %v", domains, err) log.Errorf("Error creating transaction %+v : %v", uncheckedDomains, err)
return 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) account = object.(*Account)
_, err = account.DomainsCertificate.addCertificateForDomains(certificate, domain) _, err = account.DomainsCertificate.addCertificateForDomains(certificate, domain)
if err != nil { if err != nil {
log.Errorf("Error adding ACME certificates %+v : %v", domains, err) log.Errorf("Error adding ACME certificates %+v : %v", uncheckedDomains, err)
return return
} }
if err = transaction.Commit(account); err != nil { 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) // Get provided certificate which check a domains list (Main and SANs)
// from static and dynamic provided certificates // 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) log.Debugf("Looking for provided certificate to validate %s...", domains)
cert := searchProvidedCertificateForDomains(domains, a.TLSConfig.NameToCertificate) cert := searchProvidedCertificateForDomains(domains, a.TLSConfig.NameToCertificate)
if cert == nil && a.dynamicCerts != nil && a.dynamicCerts.Get() != nil { if cert == nil && a.dynamicCerts != nil && a.dynamicCerts.Get() != nil {
cert = searchProvidedCertificateForDomains(domains, a.dynamicCerts.Get().(*traefikTls.DomainsCertificates).Get().(map[string]*tls.Certificate)) cert = searchProvidedCertificateForDomains(domains, a.dynamicCerts.Get().(*traefikTls.DomainsCertificates).Get().(map[string]*tls.Certificate))
} }
if cert == nil {
log.Debugf("No provided certificate found for domains %s, get ACME certificate.", domains) log.Debugf("No provided certificate found for domains %s, get ACME certificate.", domains)
}
return cert 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 // Use regex to test for provided certs that might have been added into TLSConfig
providedCertMatch := false for certDomains := range certs {
for k := range certs { domainCheck := false
selector := "^" + strings.Replace(k, "*.", "[^\\.]*\\.?", -1) + "$" for _, certDomain := range strings.Split(certDomains, ",") {
for _, domainToCheck := range domains { selector := "^" + strings.Replace(certDomain, "*.", "[^\\.]*\\.?", -1) + "$"
providedCertMatch, _ = regexp.MatchString(selector, domainToCheck) domainCheck, _ = regexp.MatchString(selector, domain)
if !providedCertMatch { if domainCheck {
break break
} }
} }
if providedCertMatch { if domainCheck {
log.Debugf("Got provided certificate for domains %s", domains) log.Debugf("Domain %q checked by provided certificate %q", domain, certDomains)
return certs[k] return certs[certDomains]
} }
} }
return nil 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) { func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) {
domains = fun.Map(types.CanonicalDomain, domains).([]string) domains = fun.Map(types.CanonicalDomain, domains).([]string)
log.Debugf("Loading ACME certificates %s...", domains) log.Debugf("Loading ACME certificates %s...", domains)

View file

@ -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 := make(map[string]*tls.Certificate)
mm["*.containo.us"] = &tls.Certificate{} mm["*.containo.us"] = &tls.Certificate{}
mm["traefik.acme.io"] = &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}} a := ACME{TLSConfig: &tls.Config{NameToCertificate: mm}}
domains := []string{"traefik.containo.us", "trae.containo.us"} domains := []string{"traefik.containo.us", "trae.containo.us"}
certificate := a.getProvidedCertificate(domains) uncheckedDomains := a.getUncheckedDomains(domains, nil)
assert.NotNil(t, certificate) assert.Empty(t, uncheckedDomains)
domains = []string{"traefik.acme.io", "trae.acme.io"} 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) assert.Nil(t, certificate)
} }

View file

@ -308,6 +308,9 @@ func NewTraefikConfiguration() *TraefikConfiguration {
HealthCheck: &configuration.HealthCheckConfig{ HealthCheck: &configuration.HealthCheckConfig{
Interval: flaeg.Duration(configuration.DefaultHealthCheckInterval), Interval: flaeg.Duration(configuration.DefaultHealthCheckInterval),
}, },
LifeCycle: &configuration.LifeCycle{
GraceTimeOut: flaeg.Duration(configuration.DefaultGraceTimeout),
},
CheckNewVersion: true, CheckNewVersion: true,
}, },
ConfigFile: "", ConfigFile: "",

View file

@ -569,6 +569,11 @@ Each command is described at the beginning of the help section:
```bash ```bash
traefik --help traefik --help
# or
docker run traefik[:version] --help
# ex: docker run traefik:1.5 --help
``` ```
### Command: bug ### Command: bug

View file

@ -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. 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). 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. 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`. 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. 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. 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: 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**. 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. 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`. 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. Essentially, this is the actual rule used for Layer-7 load balancing.

View file

@ -1,6 +1,7 @@
package integration package integration
import ( import (
"context"
"crypto/rand" "crypto/rand"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
@ -13,7 +14,6 @@ import (
"github.com/containous/traefik/integration/helloworld" "github.com/containous/traefik/integration/helloworld"
"github.com/containous/traefik/integration/try" "github.com/containous/traefik/integration/try"
"github.com/go-check/check" "github.com/go-check/check"
"golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
) )

View file

@ -20,7 +20,8 @@ import fmt "fmt"
import math "math" import math "math"
import ( import (
context "golang.org/x/net/context" context "context"
grpc "google.golang.org/grpc" grpc "google.golang.org/grpc"
) )

View file

@ -32,7 +32,7 @@ func NewEntryPointHandler(dstEntryPoint *configuration.EntryPoint, permanent boo
protocol = "https" protocol = "https"
} }
replacement := protocol + "://$1" + match[0] + "$2" replacement := protocol + "://${1}" + match[0] + "${2}"
return NewRegexHandler(defaultRedirectRegex, replacement, permanent) return NewRegexHandler(defaultRedirectRegex, replacement, permanent)
} }

View file

@ -1,6 +1,7 @@
package docker package docker
import ( import (
"context"
"strconv" "strconv"
"testing" "testing"
"time" "time"
@ -11,7 +12,6 @@ import (
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
dockerclient "github.com/docker/docker/client" dockerclient "github.com/docker/docker/client"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"golang.org/x/net/context"
) )
type fakeTasksClient struct { type fakeTasksClient struct {

View file

@ -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 return nil, nil
} }
@ -904,7 +905,7 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura
entryPoint := globalConfiguration.EntryPoints[entryPointName] entryPoint := globalConfiguration.EntryPoints[entryPointName]
n := negroni.New() n := negroni.New()
if entryPoint.Redirect != nil { if entryPoint.Redirect != nil && entryPointName != entryPoint.Redirect.EntryPoint {
if redirectHandlers[entryPointName] != nil { if redirectHandlers[entryPointName] != nil {
n.Use(redirectHandlers[entryPointName]) n.Use(redirectHandlers[entryPointName])
} else if handler, err := s.buildRedirectHandler(entryPointName, entryPoint.Redirect); err != nil { } 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) 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) rewrite, err := s.buildRedirectHandler(entryPointName, frontend.Redirect)
if err != nil { if err != nil {
log.Errorf("Error creating Frontend Redirect: %v", err) log.Errorf("Error creating Frontend Redirect: %v", err)

View file

@ -101,7 +101,8 @@ func (c *Certificates) CreateTLSConfig(entryPointName string) (*tls.Config, map[
for _, certificate := range *c { for _, certificate := range *c {
err := certificate.AppendCertificates(domainsCertificates, entryPointName) err := certificate.AppendCertificates(domainsCertificates, entryPointName)
if err != nil { 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 _, certDom := range domainsCertificates {
for _, cert := range certDom.Get().(map[string]*tls.Certificate) { 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() certContent, err := c.CertFile.Read()
if err != nil { if err != nil {
return err return fmt.Errorf("unable to read CertFile : %v", err)
} }
keyContent, err := c.KeyFile.Read() keyContent, err := c.KeyFile.Read()
if err != nil { if err != nil {
return err return fmt.Errorf("uUnable to read KeyFile : %v", err)
} }
tlsCert, err := tls.X509KeyPair(certContent, keyContent) tlsCert, err := tls.X509KeyPair(certContent, keyContent)
if err != nil { if err != nil {
return err return fmt.Errorf("unable to generate TLS certificate : %v", err)
} }
parsedCert, _ := x509.ParseCertificate(tlsCert.Certificate[0]) parsedCert, _ := x509.ParseCertificate(tlsCert.Certificate[0])

View file

@ -12,64 +12,63 @@ import (
"strings" "strings"
"text/tabwriter" "text/tabwriter"
"text/template" "text/template"
"time"
"github.com/containous/flaeg/parse"
flag "github.com/ogier/pflag" flag "github.com/ogier/pflag"
) )
// ErrParserNotFound is thrown when a field is flaged but not parser match its type // 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 // 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 // Flags are generated from field name or from StructTag
func getTypesRecursive(objValue reflect.Value, flagmap map[string]reflect.StructField, key string) error { func getTypesRecursive(objValue reflect.Value, flagMap map[string]reflect.StructField, key string) error {
name := key name := key
switch objValue.Kind() { switch objValue.Kind() {
case reflect.Struct: case reflect.Struct:
for i := 0; i < objValue.NumField(); i++ { for i := 0; i < objValue.NumField(); i++ {
if objValue.Type().Field(i).Anonymous { 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 return err
} }
} else if len(objValue.Type().Field(i).Tag.Get("description")) > 0 { } else if len(objValue.Type().Field(i).Tag.Get("description")) > 0 {
fieldName := objValue.Type().Field(i).Name fieldName := objValue.Type().Field(i).Name
if !isExported(fieldName) { 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 { if tag := objValue.Type().Field(i).Tag.Get("long"); len(tag) > 0 {
fieldName = tag fieldName = tag
} }
if len(key) == 0 { if len(key) == 0 {
//Lower Camel Case
//name = strings.ToLower(string(fieldName[0])) + fieldName[1:]
name = strings.ToLower(fieldName) name = strings.ToLower(fieldName)
} else { } else {
name = key + "." + strings.ToLower(fieldName) 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 return err
} }
} }
} }
case reflect.Ptr: case reflect.Ptr:
if len(key) > 0 { if len(key) > 0 {
field := flagmap[name] field := flagMap[name]
field.Type = reflect.TypeOf(false) field.Type = reflect.TypeOf(false)
flagmap[name] = field flagMap[name] = field
} }
typ := objValue.Type().Elem() typ := objValue.Type().Elem()
inst := reflect.New(typ).Elem() inst := reflect.New(typ).Elem()
if err := getTypesRecursive(inst, flagmap, name); err != nil {
if err := getTypesRecursive(inst, flagMap, name); err != nil {
return err return err
} }
default: default:
@ -78,14 +77,15 @@ func getTypesRecursive(objValue reflect.Value, flagmap map[string]reflect.Struct
return nil return nil
} }
//GetPointerFlags returns flags on pointers // GetBoolFlags returns flags on pointers
func GetBoolFlags(config interface{}) ([]string, error) { func GetBoolFlags(config interface{}) ([]string, error) {
flagmap := make(map[string]reflect.StructField) flagMap := make(map[string]reflect.StructField)
if err := getTypesRecursive(reflect.ValueOf(config), flagmap, ""); err != nil { if err := getTypesRecursive(reflect.ValueOf(config), flagMap, ""); err != nil {
return []string{}, err 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 { if structField.Type.Kind() == reflect.Bool {
flags = append(flags, f) flags = append(flags, f)
} }
@ -95,84 +95,40 @@ func GetBoolFlags(config interface{}) ([]string, error) {
// GetFlags returns flags // GetFlags returns flags
func GetFlags(config interface{}) ([]string, error) { func GetFlags(config interface{}) ([]string, error) {
flagmap := make(map[string]reflect.StructField) flagMap := make(map[string]reflect.StructField)
if err := getTypesRecursive(reflect.ValueOf(config), flagmap, ""); err != nil { if err := getTypesRecursive(reflect.ValueOf(config), flagMap, ""); err != nil {
return []string{}, err 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) flags = append(flags, f)
} }
return flags, nil return flags, nil
} }
//loadParsers loads default parsers and custom parsers given as parameter. Return a map [reflect.Type]parsers // ParseArgs : parses args return a map[flag]Getter, using parsers map[type]Getter
// bool, int, int64, uint, uint64, float64, // args must be formatted as like as flag documentation. See https://golang.org/pkg/flag
func loadParsers(customParsers map[reflect.Type]Parser) (map[reflect.Type]Parser, error) { func parseArgs(args []string, flagMap map[string]reflect.StructField, parsers map[reflect.Type]parse.Parser) (map[string]parse.Parser, error) {
parsers := map[reflect.Type]Parser{} newParsers := map[string]parse.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{}
flagSet := flag.NewFlagSet("flaeg.Load", flag.ContinueOnError) flagSet := flag.NewFlagSet("flaeg.Load", flag.ContinueOnError)
// Disable output // Disable output
flagSet.SetOutput(ioutil.Discard) flagSet.SetOutput(ioutil.Discard)
var err error var err error
for flag, structField := range flagmap { for flg, structField := range flagMap {
//for _, flag := range flags {
//structField := flagmap[flag]
if parser, ok := parsers[structField.Type]; ok { if parser, ok := parsers[structField.Type]; ok {
newparserValue := reflect.New(reflect.TypeOf(parser).Elem()) newParserValue := reflect.New(reflect.TypeOf(parser).Elem())
newparserValue.Elem().Set(reflect.ValueOf(parser).Elem()) newParserValue.Elem().Set(reflect.ValueOf(parser).Elem())
newparser := newparserValue.Interface().(Parser) newParser := newParserValue.Interface().(parse.Parser)
if short := structField.Tag.Get("short"); len(short) == 1 { if short := structField.Tag.Get("short"); len(short) == 1 {
// fmt.Printf("short : %s long : %s\n", short, flag) flagSet.VarP(newParser, flg, short, structField.Tag.Get("description"))
flagSet.VarP(newparser, flag, short, structField.Tag.Get("description"))
} else { } else {
flagSet.Var(newparser, flag, structField.Tag.Get("description")) flagSet.Var(newParser, flg, structField.Tag.Get("description"))
} }
newParsers[flag] = newparser newParsers[flg] = newParser
} else { } else {
err = ErrParserNotFound err = ErrParserNotFound
} }
@ -180,24 +136,35 @@ func parseArgs(args []string, flagmap map[string]reflect.StructField, parsers ma
// prevents case sensitivity issue // prevents case sensitivity issue
args = argsToLower(args) args = argsToLower(args)
if err := flagSet.Parse(args); err != nil { if errParse := flagSet.Parse(args); errParse != nil {
return nil, err return nil, errParse
}
// Visitor in flag.Parse
var flagList []*flag.Flag
visitor := func(fl *flag.Flag) {
flagList = append(flagList, fl)
} }
// Fill flagList with parsed flags // Fill flagList with parsed flags
flagSet.Visit(visitor) flagSet.Visit(visitor)
// Return var
valMap := make(map[string]parse.Parser)
// Return parsers on parsed flag // Return parsers on parsed flag
for _, flag := range flagList { for _, flg := range flagList {
valmap[flag.Name] = newParsers[flag.Name] 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 { func getDefaultValue(defaultValue reflect.Value, defaultPointersValue reflect.Value, defaultValmap map[string]reflect.Value, key string) error {
if defaultValue.Type() != defaultPointersValue.Type() { 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 name := key
switch defaultValue.Kind() { switch defaultValue.Kind() {
case reflect.Struct: case reflect.Struct:
@ -207,22 +174,19 @@ func getDefaultValue(defaultValue reflect.Value, defaultPointersValue reflect.Va
return err return err
} }
} else if len(defaultValue.Type().Field(i).Tag.Get("description")) > 0 { } else if len(defaultValue.Type().Field(i).Tag.Get("description")) > 0 {
name += defaultValue.Type().Name()
fieldName := defaultValue.Type().Field(i).Name fieldName := defaultValue.Type().Field(i).Name
if tag := defaultValue.Type().Field(i).Tag.Get("long"); len(tag) > 0 { if tag := defaultValue.Type().Field(i).Tag.Get("long"); len(tag) > 0 {
fieldName = tag fieldName = tag
} }
if len(key) == 0 { if len(key) == 0 {
name = strings.ToLower(fieldName) name = strings.ToLower(fieldName)
} else { } else {
name = key + "." + strings.ToLower(fieldName) name = key + "." + strings.ToLower(fieldName)
} }
if defaultValue.Field(i).Kind() != reflect.Ptr { if defaultValue.Field(i).Kind() != reflect.Ptr {
// if _, ok := defaultValmap[name]; ok {
// return errors.New("Tag already exists: " + name)
// }
defaultValmap[name] = defaultValue.Field(i) 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 { if err := getDefaultValue(defaultValue.Field(i), defaultPointersValue.Field(i), defaultValmap, name); err != nil {
return err return err
@ -238,8 +202,8 @@ func getDefaultValue(defaultValue reflect.Value, defaultPointersValue reflect.Va
return err return err
} }
defaultValmap[name] = defaultPointersNilValue defaultValmap[name] = defaultPointersNilValue
// fmt.Printf("%s: got default value %+v\n", name, defaultPointersNilValue)
} }
if !defaultValue.IsNil() { if !defaultValue.IsNil() {
if err := getDefaultValue(defaultValue.Elem(), defaultPointersValue.Elem(), defaultValmap, name); err != nil { if err := getDefaultValue(defaultValue.Elem(), defaultPointersValue.Elem(), defaultValmap, name); err != nil {
return err return err
@ -253,8 +217,8 @@ func getDefaultValue(defaultValue reflect.Value, defaultPointersValue reflect.Va
instValue := reflect.New(defaultPointersValue.Type().Elem()) instValue := reflect.New(defaultPointersValue.Type().Elem())
if len(key) != 0 { if len(key) != 0 {
defaultValmap[name] = instValue defaultValmap[name] = instValue
// fmt.Printf("%s: got default value %+v\n", name, instValue)
} }
if !defaultValue.IsNil() { if !defaultValue.IsNil() {
if err := getDefaultValue(defaultValue.Elem(), instValue.Elem(), defaultValmap, name); err != nil { if err := getDefaultValue(defaultValue.Elem(), instValue.Elem(), defaultValmap, name); err != nil {
return err return err
@ -274,13 +238,14 @@ func getDefaultValue(defaultValue reflect.Value, defaultPointersValue reflect.Va
// 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) { func setPointersNil(objValue reflect.Value) (reflect.Value, error) {
if objValue.Kind() != reflect.Ptr { 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() { } 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 { } 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()) // 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 return objValue, nil
} }
// Clone // Clone
starObjValue := objValue.Elem() starObjValue := objValue.Elem()
nilPointersObjVal := reflect.New(starObjValue.Type()) nilPointersObjVal := reflect.New(starObjValue.Type())
@ -295,39 +260,38 @@ func setPointersNil(objValue reflect.Value) (reflect.Value, error) {
return nilPointersObjVal, nil return nilPointersObjVal, nil
} }
//FillStructRecursive initialize a value of any taged Struct given by reference // FillStructRecursive initialize a value of any tagged Struct given by reference
func fillStructRecursive(objValue reflect.Value, defaultPointerValmap map[string]reflect.Value, valmap map[string]Parser, key string) error { func fillStructRecursive(objValue reflect.Value, defaultPointerValMap map[string]reflect.Value, valMap map[string]parse.Parser, key string) error {
name := key name := key
switch objValue.Kind() { switch objValue.Kind() {
case reflect.Struct: case reflect.Struct:
for i := 0; i < objValue.Type().NumField(); i++ { for i := 0; i < objValue.Type().NumField(); i++ {
if objValue.Type().Field(i).Anonymous { 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 return err
} }
} else if len(objValue.Type().Field(i).Tag.Get("description")) > 0 { } else if len(objValue.Type().Field(i).Tag.Get("description")) > 0 {
name += objValue.Type().Name()
fieldName := objValue.Type().Field(i).Name fieldName := objValue.Type().Field(i).Name
if tag := objValue.Type().Field(i).Tag.Get("long"); len(tag) > 0 { if tag := objValue.Type().Field(i).Tag.Get("long"); len(tag) > 0 {
fieldName = tag fieldName = tag
} }
if len(key) == 0 { if len(key) == 0 {
name = strings.ToLower(fieldName) name = strings.ToLower(fieldName)
} else { } else {
name = key + "." + strings.ToLower(fieldName) name = key + "." + strings.ToLower(fieldName)
} }
// fmt.Println(name)
if objValue.Field(i).Kind() != reflect.Ptr {
if val, ok := valmap[name]; ok { if objValue.Field(i).Kind() != reflect.Ptr {
// fmt.Printf("%s : set def val\n", name) if val, ok := valMap[name]; ok {
if err := setFields(objValue.Field(i), val); err != nil { if err := setFields(objValue.Field(i), val); err != nil {
return err 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 return err
} }
} }
@ -335,39 +299,38 @@ func fillStructRecursive(objValue reflect.Value, defaultPointerValmap map[string
case reflect.Ptr: case reflect.Ptr:
if len(key) == 0 && !objValue.IsNil() { if len(key) == 0 && !objValue.IsNil() {
if err := fillStructRecursive(objValue.Elem(), defaultPointerValmap, valmap, name); err != nil { return fillStructRecursive(objValue.Elem(), defaultPointerValMap, valMap, name)
return err
}
return nil
} }
contains := false contains := false
for flag := range valmap { for flg := range valMap {
// TODO replace by regexp // TODO replace by regexp
if strings.HasPrefix(flag, name+".") { if strings.HasPrefix(flg, name+".") {
contains = true contains = true
break break
} }
} }
needDefault := false needDefault := false
if _, ok := valmap[name]; ok { if _, ok := valMap[name]; ok {
needDefault = valmap[name].Get().(bool) needDefault = valMap[name].Get().(bool)
} }
if contains && objValue.IsNil() { if contains && objValue.IsNil() {
needDefault = true needDefault = true
} }
if needDefault { if needDefault {
if defVal, ok := defaultPointerValmap[name]; ok { if defVal, ok := defaultPointerValMap[name]; ok {
// set default pointer value // set default pointer value
// fmt.Printf("%s : set default value %+v\n", name, defVal)
objValue.Set(defVal) objValue.Set(defVal)
} else { } else {
return fmt.Errorf("flag %s default value not provided", name) return fmt.Errorf("flag %s default value not provided", name)
} }
} }
if !objValue.IsNil() && contains { if !objValue.IsNil() && contains {
if objValue.Type().Elem().Kind() == reflect.Struct { 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 return err
} }
} }
@ -378,35 +341,35 @@ func fillStructRecursive(objValue reflect.Value, defaultPointerValmap map[string
return nil return nil
} }
// SetFields sets value to fieldValue using tag as key in valmap // SetFields sets value to fieldValue using tag as key in valMap
func setFields(fieldValue reflect.Value, val Parser) error { func setFields(fieldValue reflect.Value, val parse.Parser) error {
if fieldValue.CanSet() { if fieldValue.CanSet() {
fieldValue.Set(reflect.ValueOf(val).Elem().Convert(fieldValue.Type())) fieldValue.Set(reflect.ValueOf(val).Elem().Convert(fieldValue.Type()))
} else { } else {
return errors.New(fieldValue.Type().String() + " is not settable.") return fmt.Errorf("%s is not settable", fieldValue.Type().String())
} }
return nil return nil
} }
// PrintHelp generates and prints command line help // 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 { 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) return PrintHelpWithCommand(flagMap, defaultValmap, parsers, nil, nil)
} }
// PrintError takes a not nil error and prints command line help // 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 { 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 { 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") { if !strings.Contains(err.Error(), ":No parser for type") {
PrintHelp(flagmap, defaultValmap, parsers) PrintHelp(flagMap, defaultValmap, parsers)
} }
return err return err
} }
// LoadWithParsers initializes config : struct fields given by reference, with args : arguments. // LoadWithParsers initializes config : struct fields given by reference, with args : arguments.
// Some custom parsers may be given. // Some custom parsers may be given.
func LoadWithParsers(config interface{}, defaultValue interface{}, args []string, customParsers map[reflect.Type]Parser) error { func LoadWithParsers(config interface{}, defaultValue interface{}, args []string, customParsers map[reflect.Type]parse.Parser) error {
cmd := &Command{ cmd := &Command{
Config: config, Config: config,
DefaultPointersConfig: defaultValue, DefaultPointersConfig: defaultValue,
@ -437,28 +400,27 @@ type Command struct {
// LoadWithCommand initializes config : struct fields given by reference, with args : arguments. // LoadWithCommand initializes config : struct fields given by reference, with args : arguments.
// Some custom parsers and some subCommand may be given. // Some custom parsers and some subCommand may be given.
func LoadWithCommand(cmd *Command, cmdArgs []string, customParsers map[reflect.Type]Parser, subCommand []*Command) error { func LoadWithCommand(cmd *Command, cmdArgs []string, customParsers map[reflect.Type]parse.Parser, subCommand []*Command) error {
parsers, err := parse.LoadParsers(customParsers)
parsers, err := loadParsers(customParsers)
if err != nil { if err != nil {
return err return err
} }
tagsmap := make(map[string]reflect.StructField) tagsMap := make(map[string]reflect.StructField)
if err := getTypesRecursive(reflect.ValueOf(cmd.Config), tagsmap, ""); err != nil { if err := getTypesRecursive(reflect.ValueOf(cmd.Config), tagsMap, ""); err != nil {
return err return err
} }
defaultValmap := make(map[string]reflect.Value) defaultValMap := make(map[string]reflect.Value)
if err := getDefaultValue(reflect.ValueOf(cmd.Config), reflect.ValueOf(cmd.DefaultPointersConfig), defaultValmap, ""); err != nil { if err := getDefaultValue(reflect.ValueOf(cmd.Config), reflect.ValueOf(cmd.DefaultPointersConfig), defaultValMap, ""); err != nil {
return err return err
} }
valmap, errParseArgs := parseArgs(cmdArgs, tagsmap, parsers) valMap, errParseArgs := parseArgs(cmdArgs, tagsMap, parsers)
if errParseArgs != nil && errParseArgs != ErrParserNotFound { 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 return err
} }
@ -470,7 +432,7 @@ func LoadWithCommand(cmd *Command, cmdArgs []string, customParsers map[reflect.T
} }
// PrintHelpWithCommand generates and prints command line help for a Command // 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 { 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 // Define a templates
// Using POSXE STD : http://pubs.opengroup.org/onlinepubs/9699919799/ // Using POSXE STD : http://pubs.opengroup.org/onlinepubs/9699919799/
const helper = `{{if .ProgDescription}}{{.ProgDescription}} const helper = `{{if .ProgDescription}}{{.ProgDescription}}
@ -514,38 +476,38 @@ Flags:
return err 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 // Sort alphabetically & Delete unparsable flags in a slice
flags := []string{} var flags []string
for flag, field := range flagmap { for flg, field := range flagMap {
if _, ok := parsers[field.Type]; ok { if _, ok := parsers[field.Type]; ok {
flags = append(flags, flag) flags = append(flags, flg)
} }
} }
sort.Strings(flags) sort.Strings(flags)
// Process data // Process data
descriptions := []string{} var descriptions []string
defaultValues := []string{} var defaultValues []string
flagsWithDashs := []string{} var flagsWithDash []string
shortFlagsWithDash := []string{} var shortFlagsWithDash []string
for _, flag := range flags { for _, flg := range flags {
field := flagmap[flag] field := flagMap[flg]
if short := field.Tag.Get("short"); len(short) == 1 { if short := field.Tag.Get("short"); len(short) == 1 {
shortFlagsWithDash = append(shortFlagsWithDash, "-"+short+",") shortFlagsWithDash = append(shortFlagsWithDash, "-"+short+",")
} else { } else {
shortFlagsWithDash = append(shortFlagsWithDash, "") shortFlagsWithDash = append(shortFlagsWithDash, "")
} }
flagsWithDashs = append(flagsWithDashs, "--"+flag) flagsWithDash = append(flagsWithDash, "--"+flg)
// flag on pointer ? // flag on pointer ?
if defVal, ok := defaultValmap[flag]; ok { if defVal, ok := defaultValMap[flg]; ok {
if defVal.Kind() != reflect.Ptr { if defVal.Kind() != reflect.Ptr {
// Set defaultValue on parsers // 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 { if defVal := parsers[field.Type].String(); len(defVal) > 0 {
@ -560,17 +522,19 @@ func printFlagsDescriptionsDefaultValues(flagmap map[string]reflect.StructField,
descriptions = append(descriptions, description) descriptions = append(descriptions, description)
if i != 0 { if i != 0 {
defaultValues = append(defaultValues, "") defaultValues = append(defaultValues, "")
flagsWithDashs = append(flagsWithDashs, "") flagsWithDash = append(flagsWithDash, "")
shortFlagsWithDash = append(shortFlagsWithDash, "") shortFlagsWithDash = append(shortFlagsWithDash, "")
} }
} }
} }
//add help flag //add help flag
shortFlagsWithDash = append(shortFlagsWithDash, "-h,") shortFlagsWithDash = append(shortFlagsWithDash, "-h,")
flagsWithDashs = append(flagsWithDashs, "--help") flagsWithDash = append(flagsWithDash, "--help")
descriptions = append(descriptions, "Print Help (this message) and exit") descriptions = append(descriptions, "Print Help (this message) and exit")
defaultValues = append(defaultValues, "") defaultValues = append(defaultValues, "")
return displayTab(output, shortFlagsWithDash, flagsWithDashs, descriptions, defaultValues)
return displayTab(output, shortFlagsWithDash, flagsWithDash, descriptions, defaultValues)
} }
func split(str string, width int) []string { func split(str string, width int) []string {
if len(str) > width { if len(str) > width {
@ -578,16 +542,19 @@ func split(str string, width int) []string {
if index == -1 { if index == -1 {
index = width index = width
} }
return append([]string{strings.TrimSpace(str[:index])}, split(strings.TrimSpace(str[index:]), width)...) return append([]string{strings.TrimSpace(str[:index])}, split(strings.TrimSpace(str[index:]), width)...)
} }
return []string{str} return []string{str}
} }
func displayTab(output io.Writer, columns ...[]string) error { func displayTab(output io.Writer, columns ...[]string) error {
nbRow := len(columns[0])
nbCol := len(columns)
w := new(tabwriter.Writer) w := new(tabwriter.Writer)
w.Init(output, 0, 4, 1, ' ', 0) w.Init(output, 0, 4, 1, ' ', 0)
nbRow := len(columns[0])
nbCol := len(columns)
for i := 0; i < nbRow; i++ { for i := 0; i < nbRow; i++ {
row := "" row := ""
for j, col := range columns { for j, col := range columns {
@ -598,16 +565,17 @@ func displayTab(output io.Writer, columns ...[]string) error {
} }
fmt.Fprintln(w, row) fmt.Fprintln(w, row)
} }
w.Flush()
return nil return w.Flush()
} }
// PrintErrorWithCommand takes a not nil error and prints command line help // 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 { 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 { if err != flag.ErrHelp {
fmt.Printf("Error here : %s\n", err) fmt.Printf("Error here : %s\n", err)
} }
PrintHelpWithCommand(flagmap, defaultValmap, parsers, cmd, subCmd)
PrintHelpWithCommand(flagMap, defaultValMap, parsers, cmd, subCmd)
return err return err
} }
@ -618,16 +586,16 @@ type Flaeg struct {
calledCommand *Command calledCommand *Command
commands []*Command ///rootCommand is th fist one in this slice commands []*Command ///rootCommand is th fist one in this slice
args []string args []string
commmandArgs []string commandArgs []string
customParsers map[reflect.Type]Parser 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 { func New(rootCommand *Command, args []string) *Flaeg {
var f Flaeg var f Flaeg
f.commands = []*Command{rootCommand} f.commands = []*Command{rootCommand}
f.args = args f.args = args
f.customParsers = map[reflect.Type]Parser{} f.customParsers = map[reflect.Type]parse.Parser{}
return &f return &f
} }
@ -637,17 +605,18 @@ func (f *Flaeg) AddCommand(command *Command) {
} }
// AddParser adds custom parser for a type to the map of custom parsers // AddParser adds custom parser for a type to the map of custom parsers
func (f *Flaeg) AddParser(typ reflect.Type, parser Parser) { func (f *Flaeg) AddParser(typ reflect.Type, parser parse.Parser) {
f.customParsers[typ] = 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 { func (f *Flaeg) Run() error {
if f.calledCommand == nil { if f.calledCommand == nil {
if _, _, err := f.findCommandWithCommandArgs(); err != nil { if _, _, err := f.findCommandWithCommandArgs(); err != nil {
return err return err
} }
} }
if _, err := f.Parse(f.calledCommand); err != nil { if _, err := f.Parse(f.calledCommand); err != nil {
return err return err
} }
@ -658,9 +627,10 @@ func (f *Flaeg) Run() error {
// It returns nil and a not nil error if it fails // It returns nil and a not nil error if it fails
func (f *Flaeg) Parse(cmd *Command) (*Command, error) { func (f *Flaeg) Parse(cmd *Command) (*Command, error) {
if f.calledCommand == nil { 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, err
} }
return cmd, nil return cmd, nil
@ -680,20 +650,20 @@ func splitArgs(args []string) (string, []string) {
// findCommandWithCommandArgs returns the called command (by reference) and command's args // findCommandWithCommandArgs returns the called command (by reference) and command's args
// the error returned is not nil if it fails // the error returned is not nil if it fails
func (f *Flaeg) findCommandWithCommandArgs() (*Command, []string, error) { func (f *Flaeg) findCommandWithCommandArgs() (*Command, []string, error) {
commandName := "" var commandName string
commandName, f.commmandArgs = splitArgs(f.args) commandName, f.commandArgs = splitArgs(f.args)
if len(commandName) > 0 { if len(commandName) > 0 {
for _, command := range f.commands { for _, command := range f.commands {
if commandName == command.Name { if commandName == command.Name {
f.calledCommand = command 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] 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) // GetCommand splits args and returns the called command (by reference)
@ -712,9 +682,11 @@ func isExported(fieldName string) bool {
if len(fieldName) < 1 { if len(fieldName) < 1 {
return false return false
} }
if string(fieldName[0]) == strings.ToUpper(string(fieldName[0])) { if string(fieldName[0]) == strings.ToUpper(string(fieldName[0])) {
return true return true
} }
return false return false
} }
@ -722,6 +694,7 @@ func argToLower(inArg string) string {
if len(inArg) < 2 { if len(inArg) < 2 {
return strings.ToLower(inArg) return strings.ToLower(inArg)
} }
var outArg string var outArg string
dashIndex := strings.Index(inArg, "--") dashIndex := strings.Index(inArg, "--")
if dashIndex == -1 { if dashIndex == -1 {
@ -732,6 +705,7 @@ func argToLower(inArg string) string {
outArg = strings.ToLower(inArg[dashIndex:dashIndex+2]) + inArg[dashIndex+2:] outArg = strings.ToLower(inArg[dashIndex:dashIndex+2]) + inArg[dashIndex+2:]
return outArg return outArg
} }
// --flag // --flag
if equalIndex := strings.Index(inArg, "="); equalIndex != -1 { if equalIndex := strings.Index(inArg, "="); equalIndex != -1 {
// --flag=value // --flag=value
@ -745,7 +719,7 @@ func argToLower(inArg string) string {
} }
func argsToLower(inArgs []string) []string { func argsToLower(inArgs []string) []string {
outArgs := make([]string, len(inArgs), len(inArgs)) outArgs := make([]string, len(inArgs))
for i, inArg := range inArgs { for i, inArg := range inArgs {
outArgs[i] = argToLower(inArg) outArgs[i] = argToLower(inArg)
} }

7
vendor/github.com/containous/flaeg/flaeg_types.go generated vendored Normal file
View file

@ -0,0 +1,7 @@
package flaeg
import "github.com/containous/flaeg/parse"
// Duration is deprecated use parse.Duration instead
// Deprecated
type Duration = parse.Duration

301
vendor/github.com/containous/flaeg/parse/parse.go generated vendored Normal file
View file

@ -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
}

View file

@ -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))
}