repos / pico

pico services mono repo
git clone https://github.com/picosh/pico.git

pico / pkg / shared
Eric Bower  ·  2026-01-25

util.go

  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 KeyForSha256(pk ssh.PublicKey) string {
 62	return ssh.FingerprintSHA256(pk)
 63}
 64
 65func GetEnv(key string, defaultVal string) string {
 66	if value, exists := os.LookupEnv(key); exists {
 67		return value
 68	}
 69
 70	return defaultVal
 71}
 72
 73// IsText reports whether a significant prefix of s looks like correct UTF-8;
 74// that is, if it is likely that s is human-readable text.
 75func IsText(s string) bool {
 76	const max = 1024 // at least utf8.UTFMax
 77	if len(s) > max {
 78		s = s[0:max]
 79	}
 80	for i, c := range s {
 81		if i+utf8.UTFMax > len(s) {
 82			// last char may be incomplete - ignore
 83			break
 84		}
 85		if c == 0xFFFD || c < ' ' && c != '\n' && c != '\t' && c != '\f' && c != '\r' {
 86			// decoding error or control character - not a text file
 87			return false
 88		}
 89	}
 90	return true
 91}
 92
 93func IsExtAllowed(filename string, allowedExt []string) bool {
 94	ext := path.Ext(filename)
 95	return slices.Contains(allowedExt, ext)
 96}
 97
 98// IsTextFile reports whether the file has a known extension indicating
 99// a text file, or if a significant chunk of the specified file looks like
100// correct UTF-8; that is, if it is likely that the file contains human-
101// readable text.
102func IsTextFile(text string) bool {
103	num := math.Min(float64(len(text)), 1024)
104	return IsText(text[0:int(num)])
105}
106
107const solarYearSecs = 31556926
108
109func TimeAgo(t *time.Time) string {
110	d := time.Since(*t)
111	var metric string
112	var amount int
113	if d.Seconds() < 60 {
114		amount = int(d.Seconds())
115		metric = "second"
116	} else if d.Minutes() < 60 {
117		amount = int(d.Minutes())
118		metric = "minute"
119	} else if d.Hours() < 24 {
120		amount = int(d.Hours())
121		metric = "hour"
122	} else if d.Seconds() < solarYearSecs {
123		amount = int(d.Hours()) / 24
124		metric = "day"
125	} else {
126		amount = int(d.Seconds()) / solarYearSecs
127		metric = "year"
128	}
129	if amount == 1 {
130		return fmt.Sprintf("%d %s ago", amount, metric)
131	} else {
132		return fmt.Sprintf("%d %ss ago", amount, metric)
133	}
134}
135
136func Shasum(data []byte) string {
137	h := sha256.New()
138	h.Write(data)
139	bs := h.Sum(nil)
140	return hex.EncodeToString(bs)
141}
142
143func BytesToMB(size int) float32 {
144	return ((float32(size) / 1000) / 1000)
145}
146
147func BytesToGB(size int) float32 {
148	return BytesToMB(size) / 1000
149}
150
151// https://stackoverflow.com/a/46964105
152func StartOfMonth() time.Time {
153	now := time.Now()
154	firstday := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC)
155	return firstday
156}
157
158func StartOfYear() time.Time {
159	now := time.Now()
160	return now.AddDate(-1, 0, 0)
161}
162
163func AnyToStr(mp map[string]any, key string) string {
164	if value, ok := mp[key]; ok {
165		if value, ok := value.(string); ok {
166			return value
167		}
168	}
169	return ""
170}
171
172func AnyToFloat(mp map[string]any, key string) float64 {
173	if value, ok := mp[key]; ok {
174		if value, ok := value.(float64); ok {
175			return value
176		}
177	}
178	return 0
179}
180
181func AnyToBool(mp map[string]any, key string) bool {
182	if value, ok := mp[key]; ok {
183		if value, ok := value.(bool); ok {
184			return value
185		}
186	}
187	return false
188}