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