320 lines
6.8 KiB
Go
320 lines
6.8 KiB
Go
package base
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"sort"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/mitchellh/mapstructure"
|
|
)
|
|
|
|
// TODO (slackpad) - Trying out a different pattern here for config handling.
|
|
// These classes support the flag.Value interface but work in a manner where
|
|
// we can tell if they have been set. This lets us work with an all-pointer
|
|
// config structure and merge it in a clean-ish way. If this ends up being a
|
|
// good pattern we should pull this out into a reusable library.
|
|
|
|
// configDecodeHook should be passed to mapstructure in order to decode into
|
|
// the *Value objects here.
|
|
var configDecodeHook = mapstructure.ComposeDecodeHookFunc(
|
|
boolToBoolValueFunc(),
|
|
stringToDurationValueFunc(),
|
|
stringToStringValueFunc(),
|
|
float64ToUintValueFunc(),
|
|
)
|
|
|
|
// boolValue provides a flag value that's aware if it has been set.
|
|
type boolValue struct {
|
|
v *bool
|
|
}
|
|
|
|
// See flag.Value.
|
|
func (b *boolValue) IsBoolFlag() bool {
|
|
return true
|
|
}
|
|
|
|
// Merge will overlay this value if it has been set.
|
|
func (b *boolValue) Merge(onto *bool) {
|
|
if b.v != nil {
|
|
*onto = *(b.v)
|
|
}
|
|
}
|
|
|
|
// See flag.Value.
|
|
func (b *boolValue) Set(v string) error {
|
|
if b.v == nil {
|
|
b.v = new(bool)
|
|
}
|
|
var err error
|
|
*(b.v), err = strconv.ParseBool(v)
|
|
return err
|
|
}
|
|
|
|
// See flag.Value.
|
|
func (b *boolValue) String() string {
|
|
var current bool
|
|
if b.v != nil {
|
|
current = *(b.v)
|
|
}
|
|
return fmt.Sprintf("%v", current)
|
|
}
|
|
|
|
// boolToBoolValueFunc is a mapstructure hook that looks for an incoming bool
|
|
// mapped to a boolValue and does the translation.
|
|
func boolToBoolValueFunc() mapstructure.DecodeHookFunc {
|
|
return func(
|
|
f reflect.Type,
|
|
t reflect.Type,
|
|
data interface{}) (interface{}, error) {
|
|
if f.Kind() != reflect.Bool {
|
|
return data, nil
|
|
}
|
|
|
|
val := boolValue{}
|
|
if t != reflect.TypeOf(val) {
|
|
return data, nil
|
|
}
|
|
|
|
val.v = new(bool)
|
|
*(val.v) = data.(bool)
|
|
return val, nil
|
|
}
|
|
}
|
|
|
|
// durationValue provides a flag value that's aware if it has been set.
|
|
type durationValue struct {
|
|
v *time.Duration
|
|
}
|
|
|
|
// Merge will overlay this value if it has been set.
|
|
func (d *durationValue) Merge(onto *time.Duration) {
|
|
if d.v != nil {
|
|
*onto = *(d.v)
|
|
}
|
|
}
|
|
|
|
// See flag.Value.
|
|
func (d *durationValue) Set(v string) error {
|
|
if d.v == nil {
|
|
d.v = new(time.Duration)
|
|
}
|
|
var err error
|
|
*(d.v), err = time.ParseDuration(v)
|
|
return err
|
|
}
|
|
|
|
// See flag.Value.
|
|
func (d *durationValue) String() string {
|
|
var current time.Duration
|
|
if d.v != nil {
|
|
current = *(d.v)
|
|
}
|
|
return current.String()
|
|
}
|
|
|
|
// stringToDurationValueFunc is a mapstructure hook that looks for an incoming
|
|
// string mapped to a durationValue and does the translation.
|
|
func stringToDurationValueFunc() mapstructure.DecodeHookFunc {
|
|
return func(
|
|
f reflect.Type,
|
|
t reflect.Type,
|
|
data interface{}) (interface{}, error) {
|
|
if f.Kind() != reflect.String {
|
|
return data, nil
|
|
}
|
|
|
|
val := durationValue{}
|
|
if t != reflect.TypeOf(val) {
|
|
return data, nil
|
|
}
|
|
if err := val.Set(data.(string)); err != nil {
|
|
return nil, err
|
|
}
|
|
return val, nil
|
|
}
|
|
}
|
|
|
|
// stringValue provides a flag value that's aware if it has been set.
|
|
type stringValue struct {
|
|
v *string
|
|
}
|
|
|
|
// Merge will overlay this value if it has been set.
|
|
func (s *stringValue) Merge(onto *string) {
|
|
if s.v != nil {
|
|
*onto = *(s.v)
|
|
}
|
|
}
|
|
|
|
// See flag.Value.
|
|
func (s *stringValue) Set(v string) error {
|
|
if s.v == nil {
|
|
s.v = new(string)
|
|
}
|
|
*(s.v) = v
|
|
return nil
|
|
}
|
|
|
|
// See flag.Value.
|
|
func (s *stringValue) String() string {
|
|
var current string
|
|
if s.v != nil {
|
|
current = *(s.v)
|
|
}
|
|
return current
|
|
}
|
|
|
|
// stringToStringValueFunc is a mapstructure hook that looks for an incoming
|
|
// string mapped to a stringValue and does the translation.
|
|
func stringToStringValueFunc() mapstructure.DecodeHookFunc {
|
|
return func(
|
|
f reflect.Type,
|
|
t reflect.Type,
|
|
data interface{}) (interface{}, error) {
|
|
if f.Kind() != reflect.String {
|
|
return data, nil
|
|
}
|
|
|
|
val := stringValue{}
|
|
if t != reflect.TypeOf(val) {
|
|
return data, nil
|
|
}
|
|
val.v = new(string)
|
|
*(val.v) = data.(string)
|
|
return val, nil
|
|
}
|
|
}
|
|
|
|
// uintValue provides a flag value that's aware if it has been set.
|
|
type uintValue struct {
|
|
v *uint
|
|
}
|
|
|
|
// Merge will overlay this value if it has been set.
|
|
func (u *uintValue) Merge(onto *uint) {
|
|
if u.v != nil {
|
|
*onto = *(u.v)
|
|
}
|
|
}
|
|
|
|
// See flag.Value.
|
|
func (u *uintValue) Set(v string) error {
|
|
if u.v == nil {
|
|
u.v = new(uint)
|
|
}
|
|
parsed, err := strconv.ParseUint(v, 0, 64)
|
|
*(u.v) = (uint)(parsed)
|
|
return err
|
|
}
|
|
|
|
// See flag.Value.
|
|
func (u *uintValue) String() string {
|
|
var current uint
|
|
if u.v != nil {
|
|
current = *(u.v)
|
|
}
|
|
return fmt.Sprintf("%v", current)
|
|
}
|
|
|
|
// float64ToUintValueFunc is a mapstructure hook that looks for an incoming
|
|
// float64 mapped to a uintValue and does the translation.
|
|
func float64ToUintValueFunc() mapstructure.DecodeHookFunc {
|
|
return func(
|
|
f reflect.Type,
|
|
t reflect.Type,
|
|
data interface{}) (interface{}, error) {
|
|
if f.Kind() != reflect.Float64 {
|
|
return data, nil
|
|
}
|
|
|
|
val := uintValue{}
|
|
if t != reflect.TypeOf(val) {
|
|
return data, nil
|
|
}
|
|
|
|
fv := data.(float64)
|
|
if fv < 0 {
|
|
return nil, fmt.Errorf("value cannot be negative")
|
|
}
|
|
|
|
// The standard guarantees at least this, and this is fine for
|
|
// values we expect to use in configs vs. being fancy with the
|
|
// machine's size for uint.
|
|
if fv > (1<<32 - 1) {
|
|
return nil, fmt.Errorf("value is too large")
|
|
}
|
|
|
|
val.v = new(uint)
|
|
*(val.v) = (uint)(fv)
|
|
return val, nil
|
|
}
|
|
}
|
|
|
|
// visitFn is a callback that gets a chance to visit each file found during a
|
|
// traversal with visit().
|
|
type visitFn func(path string) error
|
|
|
|
// visit will call the visitor function on the path if it's a file, or for each
|
|
// file in the path if it's a directory. Directories will not be recursed into,
|
|
// and files in the directory will be visited in alphabetical order.
|
|
func visit(path string, visitor visitFn) error {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return fmt.Errorf("error reading %q: %v", path, err)
|
|
}
|
|
defer f.Close()
|
|
|
|
fi, err := f.Stat()
|
|
if err != nil {
|
|
return fmt.Errorf("error checking %q: %v", path, err)
|
|
}
|
|
|
|
if !fi.IsDir() {
|
|
if err := visitor(path); err != nil {
|
|
return fmt.Errorf("error in %q: %v", path, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
contents, err := f.Readdir(-1)
|
|
if err != nil {
|
|
return fmt.Errorf("error listing %q: %v", path, err)
|
|
}
|
|
|
|
sort.Sort(dirEnts(contents))
|
|
for _, fi := range contents {
|
|
if fi.IsDir() {
|
|
continue
|
|
}
|
|
|
|
fullPath := filepath.Join(path, fi.Name())
|
|
if err := visitor(fullPath); err != nil {
|
|
return fmt.Errorf("error in %q: %v", fullPath, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// dirEnts applies sort.Interface to directory entries for sorting by name.
|
|
type dirEnts []os.FileInfo
|
|
|
|
// See sort.Interface.
|
|
func (d dirEnts) Len() int {
|
|
return len(d)
|
|
}
|
|
|
|
// See sort.Interface.
|
|
func (d dirEnts) Less(i, j int) bool {
|
|
return d[i].Name() < d[j].Name()
|
|
}
|
|
|
|
// See sort.Interface.
|
|
func (d dirEnts) Swap(i, j int) {
|
|
d[i], d[j] = d[j], d[i]
|
|
}
|