func gatherInfo(prefix string, spec interface{}) ([]varInfo, error) {
	s := reflect.ValueOf(spec)

	if s.Kind() != reflect.Ptr {
		return nil, ErrInvalidSpecification
	}
	s = s.Elem()
	if s.Kind() != reflect.Struct {
		return nil, ErrInvalidSpecification
	}
	typeOfSpec := s.Type()

	// over allocate an info array, we will extend if needed later
	infos := make([]varInfo, 0, s.NumField())
	for i := 0; i < s.NumField(); i++ {
		f := s.Field(i)
		ftype := typeOfSpec.Field(i)
		if !f.CanSet() || isTrue(ftype.Tag.Get("ignored")) {
			continue
		}

		for f.Kind() == reflect.Ptr {
			if f.IsNil() {
				if f.Type().Elem().Kind() != reflect.Struct {
					// nil pointer to a non-struct: leave it alone
					break
				}
				// nil pointer to struct: create a zero instance
				f.Set(reflect.New(f.Type().Elem()))
			}
			f = f.Elem()
		}

		// Capture information about the config variable
		info := varInfo{
			Name:  ftype.Name,
			Field: f,
			Tags:  ftype.Tag,
			Alt:   strings.ToUpper(ftype.Tag.Get("envconfig")),
		}

		// Default to the field name as the env var name (will be upcased)
		info.Key = info.Name

		// Best effort to un-pick camel casing as separate words
		if isTrue(ftype.Tag.Get("split_words")) {
			words := gatherRegexp.FindAllStringSubmatch(ftype.Name, -1)
			if len(words) > 0 {
				var name []string
				for _, words := range words {
					name = append(name, words[0])
				}

				info.Key = strings.Join(name, "_")
			}
		}
		if info.Alt != "" {
			info.Key = info.Alt
		}
		if prefix != "" {
			info.Key = fmt.Sprintf("%s_%s", prefix, info.Key)
		}
		info.Key = strings.ToUpper(info.Key)
		infos = append(infos, info)

		if f.Kind() == reflect.Struct {
			// honor Decode if present
			if decoderFrom(f) == nil && setterFrom(f) == nil && textUnmarshaler(f) == nil && binaryUnmarshaler(f) == nil  {
				innerPrefix := prefix
				if !ftype.Anonymous {
					innerPrefix = info.Key
				}

				embeddedPtr := f.Addr().Interface()
				embeddedInfos, err := gatherInfo(innerPrefix, embeddedPtr)
				if err != nil {
					return nil, err
				}
				infos = append(infos[:len(infos)-1], embeddedInfos...)

				continue
			}
		}
	}
	return infos, nil
}
func gatherInfo(prefix string, spec interface{}) ([]varInfo, error) {
	s := reflect.ValueOf(spec)

	if s.Kind() != reflect.Ptr {
		return nil, ErrInvalidSpecification
	}
	s = s.Elem()
	if s.Kind() != reflect.Struct {
		return nil, ErrInvalidSpecification
	}
	typeOfSpec := s.Type()

	
	infos := make([]varInfo, 0, s.NumField())
	for i := 0; i < s.NumField(); i++ {
		f := s.Field(i)
		ftype := typeOfSpec.Field(i)
		if !f.CanSet() || isTrue(ftype.Tag.Get("ignored")) {
			continue
		}

		for f.Kind() == reflect.Ptr {
			if f.IsNil() {
				if f.Type().Elem().Kind() != reflect.Struct {
					
					break
				}
				
				f.Set(reflect.New(f.Type().Elem()))
			}
			f = f.Elem()
		}

		
		info := varInfo{
			Name:  ftype.Name,
			Field: f,
			Tags:  ftype.Tag,
			Alt:   strings.ToUpper(ftype.Tag.Get("envconfig")),
		}

		
		info.Key = info.Name

		
		if isTrue(ftype.Tag.Get("split_words")) {
			words := gatherRegexp.FindAllStringSubmatch(ftype.Name, -1)
			if len(words) > 0 {
				var name []string
				for _, words := range words {
					name = append(name, words[0])
				}

				info.Key = strings.Join(name, "_")
			}
		}
		if info.Alt != "" {
			info.Key = info.Alt
		}
		if prefix != "" {
			info.Key = fmt.Sprintf("%s_%s", prefix, info.Key)
		}
		info.Key = strings.ToUpper(info.Key)
		infos = append(infos, info)

		if f.Kind() == reflect.Struct {
			
			if decoderFrom(f) == nil && setterFrom(f) == nil && textUnmarshaler(f) == nil && binaryUnmarshaler(f) == nil  {
				innerPrefix := prefix
				if !ftype.Anonymous {
					innerPrefix = info.Key
				}

				embeddedPtr := f.Addr().Interface()
				embeddedInfos, err := gatherInfo(innerPrefix, embeddedPtr)
				if err != nil {
					return nil, err
				}
				infos = append(infos[:len(infos)-1], embeddedInfos...)

				continue
			}
		}
	}
	return infos, nil
}
func gatherInfo ( prefix string , spec interface { } ) ( [ ] varInfo , error ) { s := reflect . ValueOf ( spec ) if s . Kind ( ) != reflect . Ptr { return nil , ErrInvalidSpecification } s = s . Elem ( ) if s . Kind ( ) != reflect . Struct { return nil , ErrInvalidSpecification } typeOfSpec := s . Type ( ) // over allocate an info array, we will extend if needed later infos := make ( [ ] varInfo , 0 , s . NumField ( ) ) for i := 0 ; i < s . NumField ( ) ; i ++ { f := s . Field ( i ) ftype := typeOfSpec . Field ( i ) if ! f . CanSet ( ) || isTrue ( ftype . Tag . Get ( " " ) ) { continue } for f . Kind ( ) == reflect . Ptr { if f . IsNil ( ) { if f . Type ( ) . Elem ( ) . Kind ( ) != reflect . Struct { // nil pointer to a non-struct: leave it alone break } // nil pointer to struct: create a zero instance f . Set ( reflect . New ( f . Type ( ) . Elem ( ) ) ) } f = f . Elem ( ) } // Capture information about the config variable info := varInfo { Name : ftype . Name , Field : f , Tags : ftype . Tag , Alt : strings . ToUpper ( ftype . Tag . Get ( " " ) ) , } // Default to the field name as the env var name (will be upcased) info . Key = info . Name // Best effort to un-pick camel casing as separate words if isTrue ( ftype . Tag . Get ( " " ) ) { words := gatherRegexp . FindAllStringSubmatch ( ftype . Name , - 1 ) if len ( words ) > 0 { var name [ ] string for _ , words := range words { name = append ( name , words [ 0 ] ) } info . Key = strings . Join ( name , " " ) } } if info . Alt != " " { info . Key = info . Alt } if prefix != " " { info . Key = fmt . Sprintf ( " " , prefix , info . Key ) } info . Key = strings . ToUpper ( info . Key ) infos = append ( infos , info ) if f . Kind ( ) == reflect . Struct { // honor Decode if present if decoderFrom ( f ) == nil && setterFrom ( f ) == nil && textUnmarshaler ( f ) == nil && binaryUnmarshaler ( f ) == nil { innerPrefix := prefix if ! ftype . Anonymous { innerPrefix = info . Key } embeddedPtr := f . Addr ( ) . Interface ( ) embeddedInfos , err := gatherInfo ( innerPrefix , embeddedPtr ) if err != nil { return nil , err } infos = append ( infos [ : len ( infos ) - 1 ] , embeddedInfos ... ) continue } } } return infos , nil }
// GatherInfo gathers information about the specified struct
GatherInfo gathers information about the specified struct