149 lines
3.5 KiB
Go
149 lines
3.5 KiB
Go
package transform
|
|
|
|
import (
|
|
"hash"
|
|
"os"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// ParseParams holds the params for a parse-modifier function.
|
|
type ParseParams struct {
|
|
Attribute string
|
|
Value interface{}
|
|
|
|
Name string
|
|
Args []string
|
|
}
|
|
|
|
// Parse runs the associated modifier function for the provided parameters.
|
|
// Depending on the type of p.Value, we may recursively run this method
|
|
// on every element of the structure.
|
|
//
|
|
// We're using reflect _quite_ heavily for this, meaning it's kind of unsafe,
|
|
// it'd be great if we could find another solution while keeping it as
|
|
// abstract as it is.
|
|
func Parse(p *ParseParams) (val interface{}, err error) {
|
|
kind := reflect.TypeOf(p.Value).Kind()
|
|
|
|
// If we have a slice/array, recursively run Parse on each element.
|
|
if kind == reflect.Slice || kind == reflect.Array {
|
|
s := reflect.ValueOf(p.Value)
|
|
for i := 0; i < s.Len(); i++ {
|
|
p.Value = s.Index(i).Interface()
|
|
if val, err = Parse(p); err != nil {
|
|
return nil, err
|
|
}
|
|
s.Index(i).Set(reflect.ValueOf(val))
|
|
}
|
|
return s.Interface(), nil
|
|
}
|
|
|
|
// If we have a map, recursively run Parse on each *key* and create a new
|
|
// map out of the return values.
|
|
if kind == reflect.Map {
|
|
result := reflect.MakeMap(reflect.TypeOf(p.Value))
|
|
for _, key := range reflect.ValueOf(p.Value).MapKeys() {
|
|
p.Value = key.Interface()
|
|
if val, err = Parse(p); err != nil {
|
|
return nil, err
|
|
}
|
|
result.SetMapIndex(reflect.ValueOf(val), reflect.ValueOf(true))
|
|
}
|
|
return result.Interface(), nil
|
|
}
|
|
|
|
// Not a slice nor a map.
|
|
switch strings.ToUpper(p.Name) {
|
|
case "FORMAT":
|
|
val, err = p.format()
|
|
case "UPPER":
|
|
val = upper(p.Value.(string))
|
|
case "LOWER":
|
|
val = lower(p.Value.(string))
|
|
case "SHA1":
|
|
val, err = p.hash(FindHash(p.Name)())
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if val == nil {
|
|
return nil, &ErrNotImplemented{p.Name, p.Attribute}
|
|
}
|
|
return val, nil
|
|
}
|
|
|
|
// format runs the correct format function based on the provided attribute.
|
|
func (p *ParseParams) format() (val interface{}, err error) {
|
|
switch p.Attribute {
|
|
case "name":
|
|
val = formatName(p.Args[0], p.Value.(string))
|
|
case "size":
|
|
val, err = p.formatSize()
|
|
case "time":
|
|
val, err = p.formatTime()
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if val == nil {
|
|
return nil, &ErrUnsupportedFormat{p.Args[0], p.Attribute}
|
|
}
|
|
return val, nil
|
|
}
|
|
|
|
// formatSize formats the size attribute. Valid arguments include `B`, `KB`,
|
|
// `MB`, and `GB` (case insensitive).
|
|
func (p *ParseParams) formatSize() (interface{}, error) {
|
|
size, err := strconv.ParseFloat(p.Value.(string), 64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch strings.ToUpper(p.Args[0]) {
|
|
case "B":
|
|
size *= 1
|
|
case "KB":
|
|
size *= 1 << 10
|
|
case "MB":
|
|
size *= 1 << 20
|
|
case "GB":
|
|
size *= 1 << 30
|
|
default:
|
|
return nil, nil
|
|
}
|
|
return size, nil
|
|
}
|
|
|
|
// formatTime formats the time attribute. Valid arguments include `ISO`,
|
|
// `UNIX`, (case insensitive) or a custom layout. If a custom layout is
|
|
// provided, it must be set according to 2006-01-02T15:04:05.999999-07:00.
|
|
func (p *ParseParams) formatTime() (interface{}, error) {
|
|
var (
|
|
t time.Time
|
|
err error
|
|
)
|
|
switch strings.ToUpper(p.Args[0]) {
|
|
case "ISO":
|
|
t, err = time.Parse(time.RFC3339, p.Value.(string))
|
|
case "UNIX":
|
|
t, err = time.Parse(time.UnixDate, p.Value.(string))
|
|
default:
|
|
t, err = time.Parse(p.Args[0], p.Value.(string))
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return t, nil
|
|
}
|
|
|
|
func (p *ParseParams) hash(h hash.Hash) (interface{}, error) {
|
|
info, err := os.Stat(p.Value.(string))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ComputeHash(info, p.Value.(string), h)
|
|
}
|