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}