Skip to content

Commit 233de7f

Browse files
committed
Add new functions to custom-api widget
1 parent c6e0230 commit 233de7f

File tree

2 files changed

+115
-3
lines changed

2 files changed

+115
-3
lines changed

Diff for: docs/custom-api.md

+12-1
Original file line numberDiff line numberDiff line change
@@ -325,13 +325,24 @@ The following helper functions provided by Glance are available:
325325
- `toFloat(i int) float`: Converts an integer to a float.
326326
- `toInt(f float) int`: Converts a float to an integer.
327327
- `toRelativeTime(t time.Time) template.HTMLAttr`: Converts Time to a relative time such as 2h, 1d, etc which dynamically updates. **NOTE:** the value of this function should be used as an attribute in an HTML tag, e.g. `<span {{ toRelativeTime .Time }}></span>`.
328-
- `parseTime(layout string, s string) time.Time`: Parses a string into time.Time. The layout must be provided in Go's [date format](https://pkg.go.dev/time#pkg-constants). You can alternatively use these values instead of the literal format: "RFC3339", "RFC3339Nano", "DateTime", "DateOnly", "TimeOnly".
328+
- `parseTime(layout string, s string) time.Time`: Parses a string into time.Time. The layout must be provided in Go's [date format](https://pkg.go.dev/time#pkg-constants). You can alternatively use these values instead of the literal format: "unix", "RFC3339", "RFC3339Nano", "DateTime", "DateOnly".
329+
- `parseRelativeTime(layout string, s string) time.Time`: A shorthand for `{{ .String "date" | parseTime "rfc3339" | toRelativeTime }}`.
329330
- `add(a, b float) float`: Adds two numbers.
330331
- `sub(a, b float) float`: Subtracts two numbers.
331332
- `mul(a, b float) float`: Multiplies two numbers.
332333
- `div(a, b float) float`: Divides two numbers.
333334
- `formatApproxNumber(n int) string`: Formats a number to be more human-readable, e.g. 1000 -> 1k.
334335
- `formatNumber(n float|int) string`: Formats a number with commas, e.g. 1000 -> 1,000.
336+
- `trimPrefix(prefix string, str string) string`: Trims the prefix from a string.
337+
- `trimSuffix(suffix string, str string) string`: Trims the suffix from a string.
338+
- `trimSpace(str string) string`: Trims whitespace from a string on both ends.
339+
- `replaceAll(old string, new string, str string) string`: Replaces all occurrences of a string in a string.
340+
- `findMatch(pattern string, str string) string`: Finds the first match of a regular expression in a string.
341+
- `findSubmatch(pattern string, str string) string`: Finds the first submatch of a regular expression in a string.
342+
- `sortByString(key string, order string, arr []JSON): []JSON`: Sorts an array of JSON objects by a string key in either ascending or descending order.
343+
- `sortByInt(key string, order string, arr []JSON): []JSON`: Sorts an array of JSON objects by an integer key in either ascending or descending order.
344+
- `sortByFloat(key string, order string, arr []JSON): []JSON`: Sorts an array of JSON objects by a float key in either ascending or descending order.
345+
- `sortByTime(key string, layout string, order string, arr []JSON): []JSON`: Sorts an array of JSON objects by a time key in either ascending or descending order. The format must be provided in Go's [date format](https://pkg.go.dev/time#pkg-constants).
335346

336347
The following helper functions provided by Go's `text/template` are available:
337348

Diff for: internal/glance/widget-custom-api.go

+103-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import (
1111
"log/slog"
1212
"math"
1313
"net/http"
14+
"regexp"
15+
"sort"
16+
"strconv"
1417
"strings"
1518
"sync"
1619
"time"
@@ -340,6 +343,22 @@ func (r *decoratedGJSONResult) Bool(key string) bool {
340343
}
341344

342345
var customAPITemplateFuncs = func() template.FuncMap {
346+
var regexpCacheMu sync.Mutex
347+
var regexpCache = make(map[string]*regexp.Regexp)
348+
349+
getCachedRegexp := func(pattern string) *regexp.Regexp {
350+
regexpCacheMu.Lock()
351+
defer regexpCacheMu.Unlock()
352+
353+
regex, exists := regexpCache[pattern]
354+
if !exists {
355+
regex = regexp.MustCompile(pattern)
356+
regexpCache[pattern] = regex
357+
}
358+
359+
return regex
360+
}
361+
343362
funcs := template.FuncMap{
344363
"toFloat": func(a int) float64 {
345364
return float64(a)
@@ -369,6 +388,83 @@ var customAPITemplateFuncs = func() template.FuncMap {
369388
// Shorthand to do both of the above with a single function call
370389
return dynamicRelativeTimeAttrs(customAPIFuncParseTime(layout, value))
371390
},
391+
// The reason we flip the parameter order is so that you can chain multiple calls together like this:
392+
// {{ .JSON.String "foo" | trimPrefix "bar" | doSomethingElse }}
393+
// instead of doing this:
394+
// {{ trimPrefix (.JSON.String "foo") "bar" | doSomethingElse }}
395+
// since the piped value gets passed as the last argument to the function.
396+
"trimPrefix": func(prefix, s string) string {
397+
return strings.TrimPrefix(s, prefix)
398+
},
399+
"trimSuffix": func(suffix, s string) string {
400+
return strings.TrimSuffix(s, suffix)
401+
},
402+
"trimSpace": strings.TrimSpace,
403+
"replaceAll": func(old, new, s string) string {
404+
return strings.ReplaceAll(s, old, new)
405+
},
406+
"findMatch": func(pattern, s string) string {
407+
if s == "" {
408+
return ""
409+
}
410+
411+
return getCachedRegexp(pattern).FindString(s)
412+
},
413+
"findSubmatch": func(pattern, s string) string {
414+
if s == "" {
415+
return ""
416+
}
417+
418+
regex := getCachedRegexp(pattern)
419+
return itemAtIndexOrDefault(regex.FindStringSubmatch(s), 1, "")
420+
},
421+
"sortByString": func(key, order string, results []decoratedGJSONResult) []decoratedGJSONResult {
422+
sort.Slice(results, func(a, b int) bool {
423+
if order == "asc" {
424+
return results[a].String(key) < results[b].String(key)
425+
}
426+
427+
return results[a].String(key) > results[b].String(key)
428+
})
429+
430+
return results
431+
},
432+
"sortByInt": func(key, order string, results []decoratedGJSONResult) []decoratedGJSONResult {
433+
sort.Slice(results, func(a, b int) bool {
434+
if order == "asc" {
435+
return results[a].Int(key) < results[b].Int(key)
436+
}
437+
438+
return results[a].Int(key) > results[b].Int(key)
439+
})
440+
441+
return results
442+
},
443+
"sortByFloat": func(key, order string, results []decoratedGJSONResult) []decoratedGJSONResult {
444+
sort.Slice(results, func(a, b int) bool {
445+
if order == "asc" {
446+
return results[a].Float(key) < results[b].Float(key)
447+
}
448+
449+
return results[a].Float(key) > results[b].Float(key)
450+
})
451+
452+
return results
453+
},
454+
"sortByTime": func(key, layout, order string, results []decoratedGJSONResult) []decoratedGJSONResult {
455+
sort.Slice(results, func(a, b int) bool {
456+
timeA := customAPIFuncParseTime(layout, results[a].String(key))
457+
timeB := customAPIFuncParseTime(layout, results[b].String(key))
458+
459+
if order == "asc" {
460+
return timeA.Before(timeB)
461+
}
462+
463+
return timeA.After(timeB)
464+
})
465+
466+
return results
467+
},
372468
}
373469

374470
for key, value := range globalTemplateFunctions {
@@ -382,6 +478,13 @@ var customAPITemplateFuncs = func() template.FuncMap {
382478

383479
func customAPIFuncParseTime(layout, value string) time.Time {
384480
switch strings.ToLower(layout) {
481+
case "unix":
482+
asInt, err := strconv.ParseInt(value, 10, 64)
483+
if err != nil {
484+
return time.Unix(0, 0)
485+
}
486+
487+
return time.Unix(asInt, 0)
385488
case "rfc3339":
386489
layout = time.RFC3339
387490
case "rfc3339nano":
@@ -390,8 +493,6 @@ func customAPIFuncParseTime(layout, value string) time.Time {
390493
layout = time.DateTime
391494
case "dateonly":
392495
layout = time.DateOnly
393-
case "timeonly":
394-
layout = time.TimeOnly
395496
}
396497

397498
parsed, err := time.Parse(layout, value)

0 commit comments

Comments
 (0)