Files
kashav--fsql/transform/parse.go
T
2026-07-13 12:32:14 +08:00

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