main pico / pkg / shared / util.go
Antonio Mika  ·  2026-10-10
  1package shared
  2
  3import (
  4	"crypto/sha256"
  5	"encoding/base64"
  6	"encoding/hex"
  7	"fmt"
  8	"math"
  9	"os"
 10	"path"
 11	"path/filepath"
 12	"regexp"
 13	"strings"
 14	"time"
 15	"unicode"
 16	"unicode/utf8"
 17
 18	"slices"
 19
 20	"golang.org/x/crypto/ssh"
 21)
 22
 23var fnameRe = regexp.MustCompile(`[-_]+`)
 24var subdomainRe = regexp.MustCompile(`^[a-z0-9-]+$`)
 25
 26var KB = 1000
 27var MB = KB * 1000
 28
 29func IsValidSubdomain(subd string) bool {
 30	return subdomainRe.MatchString(subd)
 31}
 32
 33func FilenameToTitle(filename string, title string) string {
 34	if filename != title {
 35		return title
 36	}
 37
 38	return ToUpper(title)
 39}
 40
 41func ToUpper(str string) string {
 42	pre := fnameRe.ReplaceAllString(str, " ")
 43
 44	r := []rune(pre)
 45	if len(r) > 0 {
 46		r[0] = unicode.ToUpper(r[0])
 47	}
 48
 49	return string(r)
 50}
 51
 52func SanitizeFileExt(fname string) string {
 53	return strings.TrimSuffix(fname, filepath.Ext(fname))
 54}
 55
 56func KeyForKeyText(pk ssh.PublicKey) string {
 57	kb := base64.StdEncoding.EncodeToString(pk.Marshal())
 58	return fmt.Sprintf("%s %s", pk.Type(), kb)
 59}
 60
 61func GetEnv(key string, defaultVal string) string {
 62	if value, exists := os.LookupEnv(key); exists {
 63		return value
 64	}
 65
 66	return defaultVal
 67}
 68
 69// IsText reports whether a significant prefix of s looks like correct UTF-8;
 70// that is, if it is likely that s is human-readable text.
 71func IsText(s string) bool {
 72	const max = 1024 // at least utf8.UTFMax
 73	if len(s) > max {
 74		s = s[0:max]
 75	}
 76	for i, c := range s {
 77		if i+utf8.UTFMax > len(s) {
 78			// last char may be incomplete - ignore
 79			break
 80		}
 81		if c == 0xFFFD || c < ' ' && c != '\n' && c != '\t' && c != '\f' && c != '\r' {
 82			// decoding error or control character - not a text file
 83			return false
 84		}
 85	}
 86	return true
 87}
 88
 89func IsExtAllowed(filename string, allowedExt []string) bool {
 90	ext := path.Ext(filename)
 91	return slices.Contains(allowedExt, ext)
 92}
 93
 94// IsTextFile reports whether the file has a known extension indicating
 95// a text file, or if a significant chunk of the specified file looks like
 96// correct UTF-8; that is, if it is likely that the file contains human-
 97// readable text.
 98func IsTextFile(text string) bool {
 99	num := math.Min(float64(len(text)), 1024)
100	return IsText(text[0:int(num)])
101}
102
103const solarYearSecs = 31556926
104
105func TimeAgo(t *time.Time) string {
106	d := time.Since(*t)
107	var metric string
108	var amount int
109	if d.Seconds() < 60 {
110		amount = int(d.Seconds())
111		metric = "second"
112	} else if d.Minutes() < 60 {
113		amount = int(d.Minutes())
114		metric = "minute"
115	} else if d.Hours() < 24 {
116		amount = int(d.Hours())
117		metric = "hour"
118	} else if d.Seconds() < solarYearSecs {
119		amount = int(d.Hours()) / 24
120		metric = "day"
121	} else {
122		amount = int(d.Seconds()) / solarYearSecs
123		metric = "year"
124	}
125	if amount == 1 {
126		return fmt.Sprintf("%d %s ago", amount, metric)
127	} else {
128		return fmt.Sprintf("%d %ss ago", amount, metric)
129	}
130}
131
132func Shasum(data []byte) string {
133	h := sha256.New()
134	h.Write(data)
135	bs := h.Sum(nil)
136	return hex.EncodeToString(bs)
137}
138
139func BytesToMB(size int) float32 {
140	return ((float32(size) / 1000) / 1000)
141}
142
143func BytesToGB(size int) float32 {
144	return BytesToMB(size) / 1000
145}
146
147// StorageUsage is the note an upload prints about storage. It is empty
148// until a user reaches their quota.
149func StorageUsage(used, max int) string {
150	if max <= 0 || used < max {
151		return ""
152	}
153	pct := float32(used) / float32(max) * 100
154	return fmt.Sprintf(" (space: %.2f/%.2fGB, %.2f%%)", BytesToGB(used), BytesToGB(max), pct)
155}
156
157// https://stackoverflow.com/a/46964105
158func StartOfMonth() time.Time {
159	now := time.Now()
160	firstday := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC)
161	return firstday
162}
163
164func StartOfYear() time.Time {
165	now := time.Now()
166	return now.AddDate(-1, 0, 0)
167}
168
169func AnyToStr(mp map[string]any, key string) string {
170	if value, ok := mp[key]; ok {
171		if value, ok := value.(string); ok {
172			return value
173		}
174	}
175	return ""
176}
177
178func AnyToFloat(mp map[string]any, key string) float64 {
179	if value, ok := mp[key]; ok {
180		if value, ok := value.(float64); ok {
181			return value
182		}
183	}
184	return 0
185}
186
187func AnyToBool(mp map[string]any, key string) bool {
188	if value, ok := mp[key]; ok {
189		if value, ok := value.(bool); ok {
190			return value
191		}
192	}
193	return false
194}