repos / pico

pico services - prose.sh, pastes.sh, imgs.sh, feeds.sh, pgs.sh
git clone https://github.com/picosh/pico.git

pico / shared
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}