mirror of
https://github.com/sbrow/thor.git
synced 2026-08-26 11:23:32 -04:00
feat: Added Go style date format configuration.
This commit is contained in:
+61
-41
@@ -1,6 +1,7 @@
|
||||
package mustache
|
||||
|
||||
import "core:fmt"
|
||||
import "core:log"
|
||||
import "core:reflect"
|
||||
import "core:strings"
|
||||
import "core:time"
|
||||
@@ -12,6 +13,8 @@ MAX_PIPES :: 8
|
||||
// No filter accepts more than 2 args.
|
||||
MAX_PIPE_ARGS :: 2
|
||||
|
||||
DEFAULT_DATE_FORMAT :: "2 Jan 2006"
|
||||
|
||||
Pipe_Filter :: struct {
|
||||
op: string,
|
||||
args: [dynamic; MAX_PIPE_ARGS]string,
|
||||
@@ -111,6 +114,7 @@ apply_pipeline :: proc(
|
||||
current = value
|
||||
for &filter in filters {
|
||||
current = apply_filter(current, &filter, pos, ctx) or_return
|
||||
log.debugf("applied: filter=%v before=%s after=%s pos=%d", filter, value, current, pos)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -128,11 +132,42 @@ apply_filter :: proc(value: any, filter: ^Pipe_Filter, pos: int, ctx: []any) ->
|
||||
pos = pos,
|
||||
kind = .Data,
|
||||
}
|
||||
} else {
|
||||
return apply_format(str, filter.args[:], pos)
|
||||
}
|
||||
|
||||
date_format: string
|
||||
|
||||
if len(filter.args) > 0 {
|
||||
date_format = filter.args[0]
|
||||
} else {
|
||||
date_format_raw := resolve_name("date_format", ctx)
|
||||
if date_format_raw == nil {
|
||||
return value, Error_Body {
|
||||
msg = "Unable to determine date format",
|
||||
pos = pos,
|
||||
kind = .Data,
|
||||
}
|
||||
} else {
|
||||
valid: bool
|
||||
date_format, valid = reflect.as_string(date_format_raw)
|
||||
if !valid {
|
||||
return value, Error_Body {
|
||||
msg = "date format is not a string",
|
||||
pos = pos,
|
||||
kind = .Data,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
str2, err := apply_format(str, filter.args[:], pos, date_format)
|
||||
if err != nil {
|
||||
return value, err
|
||||
} else {
|
||||
return any{new_clone(str2, context.temp_allocator), typeid_of(string)}, nil
|
||||
}
|
||||
|
||||
case:
|
||||
return nil, Error_Body{
|
||||
return nil, Error_Body {
|
||||
msg = fmt.tprintf("unknown pipe op '%s'", filter.op),
|
||||
pos = pos,
|
||||
kind = .Data,
|
||||
@@ -140,51 +175,40 @@ apply_filter :: proc(value: any, filter: ^Pipe_Filter, pos: int, ctx: []any) ->
|
||||
}
|
||||
}
|
||||
|
||||
// apply_format formats an ISO 8601 date string as a display string
|
||||
// (e.g. "2026-03-15T08:49:54-04:00" → "15 Mar 2026"). Invalid input
|
||||
// (empty, too-short, or unparseable) returns a Data-kind `Error`. Templates
|
||||
// that need to skip dateless pages should gate with a section:
|
||||
// {{#date}}<time datetime="{{.}}">{{. | format}}</time>{{/date}}
|
||||
// The section's truthiness check catches empty before the filter runs.
|
||||
//
|
||||
// Currently ignores args; planned to accept Go reference-date format
|
||||
// strings in the future.
|
||||
//
|
||||
// Accepts any of these ISO 8601 forms (date prefix is invariant):
|
||||
// 2023-10-15T13:18:50-07:00
|
||||
// 2023-10-15T13:18:50-0700
|
||||
// 2023-10-15T13:18:50Z
|
||||
// 2023-10-15T13:18:50
|
||||
// 2023-10-15
|
||||
apply_format :: proc(iso: string, args: []string, pos: int) -> (result: any, err: Error) {
|
||||
if len(iso) < 10 {
|
||||
return nil, Error_Body{
|
||||
msg = "format may only be used on dates",
|
||||
pos = pos,
|
||||
kind = .Data,
|
||||
}
|
||||
apply_format :: proc(
|
||||
iso: string,
|
||||
args: []string,
|
||||
pos: int,
|
||||
date_format: string,
|
||||
) -> (
|
||||
result: string,
|
||||
err: Error,
|
||||
) {
|
||||
fmt_str := date_format
|
||||
if fmt_str == "" {
|
||||
log.errorf(
|
||||
"format pipe used but no date format configured (set date.format in thor.json) Default will be used",
|
||||
)
|
||||
fmt_str = DEFAULT_DATE_FORMAT
|
||||
}
|
||||
|
||||
year := iso[:4]
|
||||
month_num := (int(iso[5]) - 0x30) * 10 + (int(iso[6]) - 0x30)
|
||||
day_num := (int(iso[8]) - 0x30) * 10 + (int(iso[9]) - 0x30)
|
||||
|
||||
if month_num < 1 || month_num > 12 {
|
||||
return nil, Error_Body{
|
||||
components, ok := parse_iso_date(iso)
|
||||
if !ok {
|
||||
return "", Error_Body {
|
||||
msg = fmt.tprintf("invalid date: \"%s\"", iso),
|
||||
pos = pos,
|
||||
kind = .Data,
|
||||
}
|
||||
}
|
||||
|
||||
month := fmt.tprintf("%s", time.Month(month_num))[:3]
|
||||
return fmt.tprintf("%d %s %s", day_num, month, year), nil
|
||||
log.debugf("date: '%s' format: '%s'", iso, date_format)
|
||||
return format_date(components, fmt_str), nil
|
||||
}
|
||||
|
||||
// Groups preserve first-appearance order from the input list.
|
||||
apply_group_by :: proc(value: any, args: []string, pos: int) -> (result: any, err: Error) {
|
||||
if len(args) != 1 {
|
||||
return nil, Error_Body{
|
||||
return nil, Error_Body {
|
||||
msg = fmt.tprintf("group_by expects 1 argument, got %d", len(args)),
|
||||
pos = pos,
|
||||
kind = .Data,
|
||||
@@ -194,11 +218,7 @@ apply_group_by :: proc(value: any, args: []string, pos: int) -> (result: any, er
|
||||
|
||||
elem_info, count, data := list_info(value)
|
||||
if elem_info == nil {
|
||||
return nil, Error_Body{
|
||||
msg = "group_by expects a list",
|
||||
pos = pos,
|
||||
kind = .Data,
|
||||
}
|
||||
return nil, Error_Body{msg = "group_by expects a list", pos = pos, kind = .Data}
|
||||
}
|
||||
|
||||
groups := make([dynamic]Group, 0, 8, context.temp_allocator)
|
||||
@@ -219,7 +239,7 @@ apply_group_by :: proc(value: any, args: []string, pos: int) -> (result: any, er
|
||||
}
|
||||
key_str := any_to_string(key_val)
|
||||
if len(key_str) == 0 {
|
||||
return nil, Error_Body{
|
||||
return nil, Error_Body {
|
||||
msg = fmt.tprintf("group_by: field '%s' is empty", field),
|
||||
pos = pos,
|
||||
kind = .Data,
|
||||
|
||||
Reference in New Issue
Block a user