112 lines
3.3 KiB
Go
112 lines
3.3 KiB
Go
package builtinfunc
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/larvit/fejkdata/internal/drawstate"
|
|
"github.com/larvit/fejkdata/internal/invariant"
|
|
)
|
|
|
|
const dayLayout = "2006-01-02"
|
|
|
|
// quotedLayout reports whether an arg carries the single quotes a layout is written in.
|
|
func quotedLayout(a string) bool {
|
|
return len(a) >= 2 && a[0] == '\'' && a[len(a)-1] == '\''
|
|
}
|
|
|
|
// layoutArg is the Go layout a quoted arg holds, refused when unquoted.
|
|
// docs/decisions.md#a-layout-is-always-quoted
|
|
func layoutArg(a string) (string, error) {
|
|
if !quotedLayout(a) {
|
|
bare := strings.Trim(a, `'"`)
|
|
if strings.HasPrefix(a, `"`) || strings.HasSuffix(a, `"`) {
|
|
return "", fmt.Errorf("layout %s is double-quoted; write '%s'", a, bare)
|
|
}
|
|
return "", fmt.Errorf("layout %s is not quoted; write '%s'", a, bare)
|
|
}
|
|
return a[1 : len(a)-1], nil
|
|
}
|
|
|
|
// layoutOf is layoutArg for an arg a check already validated.
|
|
func layoutOf(a string) string {
|
|
layout, err := layoutArg(a)
|
|
if err != nil {
|
|
panic(invariant.Broken("builtin arg %q reached prep unvalidated: %v", a, err))
|
|
}
|
|
return layout
|
|
}
|
|
|
|
// layoutArity checks a call ending in a layout takes n args, naming the quoted
|
|
// layout where an unquoted one split into more.
|
|
func layoutArity(name string, n int, a []string) error {
|
|
if len(a) == n {
|
|
return nil
|
|
}
|
|
hint := ""
|
|
if len(a) > n && !holdsQuotedLayout(a[n-1:]) {
|
|
hint = fmt.Sprintf("; a layout holding a comma is quoted: '%s'", strings.Trim(strings.Join(a[n-1:], ", "), `'"`))
|
|
}
|
|
return fmt.Errorf("%s takes %d argument%s, got %d%s", name, n, plural(n), len(a), hint)
|
|
}
|
|
|
|
// holdsQuotedLayout reports whether the surplus args already carry a quoted layout,
|
|
// which no comma split apart.
|
|
func holdsQuotedLayout(a []string) bool {
|
|
for _, arg := range a {
|
|
if quotedLayout(arg) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
func dateArgs(a []string) error {
|
|
if err := layoutArity("date", 3, a); err != nil {
|
|
return err
|
|
}
|
|
from, err := time.Parse(dayLayout, a[0])
|
|
if err != nil {
|
|
return fmt.Errorf("date(from,to,layout): from %q is not a YYYY-MM-DD date", a[0])
|
|
}
|
|
to, err := time.Parse(dayLayout, a[1])
|
|
if err != nil {
|
|
return fmt.Errorf("date(from,to,layout): to %q is not a YYYY-MM-DD date", a[1])
|
|
}
|
|
if to.Before(from) {
|
|
return fmt.Errorf("date(from,to,layout): from %s is after to %s", a[0], a[1])
|
|
}
|
|
_, err = layoutArg(a[2])
|
|
return err
|
|
}
|
|
func timeArg(a []string) error {
|
|
if err := layoutArity("time", 1, a); err != nil {
|
|
return err
|
|
}
|
|
_, err := layoutArg(a[0])
|
|
return err
|
|
}
|
|
|
|
// datePrep draws a second in [from 00:00:00, to 23:59:59] UTC; the span is counted
|
|
// in seconds, since a Duration overflows past 292 years.
|
|
func datePrep(a []string) Call {
|
|
from, err := time.Parse(dayLayout, a[0])
|
|
if err != nil {
|
|
panic(invariant.Broken("builtin arg %q reached prep unvalidated: %v", a[0], err))
|
|
}
|
|
to, err := time.Parse(dayLayout, a[1])
|
|
if err != nil {
|
|
panic(invariant.Broken("builtin arg %q reached prep unvalidated: %v", a[1], err))
|
|
}
|
|
layout, start, span := layoutOf(a[2]), from.Unix(), int(to.Unix()-from.Unix())+86400
|
|
return func(s *drawstate.State, _ string, _ []string) string {
|
|
return time.Unix(start+int64(s.IntN(span)), 0).UTC().Format(layout)
|
|
}
|
|
}
|
|
func timePrep(a []string) Call {
|
|
layout := layoutOf(a[0])
|
|
return func(s *drawstate.State, _ string, _ []string) string {
|
|
return time.Unix(int64(s.IntN(86400)), 0).UTC().Format(layout)
|
|
}
|
|
}
|