274 lines
5.9 KiB
Go
274 lines
5.9 KiB
Go
package util
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"math/big"
|
|
"path/filepath"
|
|
"regexp"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"unicode"
|
|
|
|
"github.com/dustin/go-humanize"
|
|
)
|
|
|
|
func Bytes(size uint64) string {
|
|
switch runtime.GOOS {
|
|
case "darwin":
|
|
return humanize.Bytes(size)
|
|
default:
|
|
return humanize.IBytes(size)
|
|
}
|
|
}
|
|
|
|
func Decode(s string) string {
|
|
decoded, err := base64.StdEncoding.DecodeString(s)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return string(decoded)
|
|
}
|
|
|
|
func GenerateCryptoID() string {
|
|
bytes := make([]byte, 16)
|
|
if _, err := rand.Read(bytes); err != nil {
|
|
panic(err)
|
|
}
|
|
return hex.EncodeToString(bytes)
|
|
}
|
|
|
|
func IsMostlyLatinString(str string) bool {
|
|
if len(str) <= 0 {
|
|
return false
|
|
}
|
|
latinLength := 0
|
|
nonLatinLength := 0
|
|
for _, r := range str {
|
|
if isLatinRune(r) {
|
|
latinLength++
|
|
} else {
|
|
nonLatinLength++
|
|
}
|
|
}
|
|
return latinLength > nonLatinLength
|
|
}
|
|
|
|
func isLatinRune(r rune) bool {
|
|
return unicode.In(r, unicode.Latin)
|
|
}
|
|
|
|
// ToHumanReadableSpeed converts an integer representing bytes per second to a human-readable format using binary notation
|
|
func ToHumanReadableSpeed(bytesPerSecond int) string {
|
|
if bytesPerSecond <= 0 {
|
|
return `0 KiB/s`
|
|
}
|
|
|
|
const unit = 1024
|
|
if bytesPerSecond < unit {
|
|
return fmt.Sprintf("%d B/s", bytesPerSecond)
|
|
}
|
|
div, exp := int64(unit), 0
|
|
for n := int64(bytesPerSecond) / unit; n >= unit; n /= unit {
|
|
div *= unit
|
|
exp++
|
|
}
|
|
return fmt.Sprintf("%.1f %ciB/s", float64(bytesPerSecond)/float64(div), "KMGTPE"[exp])
|
|
}
|
|
|
|
func StringSizeToBytes(str string) (int64, error) {
|
|
// Regular expression to extract size and unit
|
|
re := regexp.MustCompile(`(?i)^(\d+(\.\d+)?)\s*([KMGT]?i?B)$`)
|
|
|
|
match := re.FindStringSubmatch(strings.TrimSpace(str))
|
|
if match == nil {
|
|
return 0, fmt.Errorf("invalid size format: %s", str)
|
|
}
|
|
|
|
// Extract the numeric part and convert to float64
|
|
size, err := strconv.ParseFloat(match[1], 64)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to parse size: %s", err)
|
|
}
|
|
|
|
// Extract the unit and convert to lowercase
|
|
unit := strings.ToLower(match[3])
|
|
|
|
// Map units to their respective multipliers
|
|
unitMultipliers := map[string]int64{
|
|
"b": 1,
|
|
"bi": 1,
|
|
"kb": 1024,
|
|
"kib": 1024,
|
|
"mb": 1024 * 1024,
|
|
"mib": 1024 * 1024,
|
|
"gb": 1024 * 1024 * 1024,
|
|
"gib": 1024 * 1024 * 1024,
|
|
"tb": 1024 * 1024 * 1024 * 1024,
|
|
"tib": 1024 * 1024 * 1024 * 1024,
|
|
}
|
|
|
|
// Apply the multiplier based on the unit
|
|
multiplier, ok := unitMultipliers[unit]
|
|
if !ok {
|
|
return 0, fmt.Errorf("invalid unit: %s", unit)
|
|
}
|
|
|
|
// Calculate the total bytes
|
|
bytes := int64(size * float64(multiplier))
|
|
return bytes, nil
|
|
}
|
|
|
|
// FormatETA formats an ETA (in seconds) into a human-readable string
|
|
func FormatETA(etaInSeconds int) string {
|
|
const noETA = 8640000
|
|
|
|
if etaInSeconds == noETA {
|
|
return "No ETA"
|
|
}
|
|
|
|
etaDuration := time.Duration(etaInSeconds) * time.Second
|
|
|
|
hours := int(etaDuration.Hours())
|
|
minutes := int(etaDuration.Minutes()) % 60
|
|
seconds := int(etaDuration.Seconds()) % 60
|
|
|
|
switch {
|
|
case hours > 0:
|
|
return fmt.Sprintf("%d hours left", hours)
|
|
case minutes > 0:
|
|
return fmt.Sprintf("%d minutes left", minutes)
|
|
case seconds < 0:
|
|
return "No ETA"
|
|
default:
|
|
return fmt.Sprintf("%d seconds left", seconds)
|
|
}
|
|
}
|
|
|
|
func Pluralize(count int, singular, plural string) string {
|
|
if count == 1 {
|
|
return singular
|
|
}
|
|
return plural
|
|
}
|
|
|
|
// NormalizePath normalizes a path by converting it to lowercase and replacing backslashes with forward slashes
|
|
// Warning: Do not use the returned string for anything filesystem related, only for comparison
|
|
func NormalizePath(path string) (ret string) {
|
|
return strings.ToLower(filepath.ToSlash(path))
|
|
}
|
|
|
|
func Base64EncodeStr(str string) string {
|
|
return base64.StdEncoding.EncodeToString([]byte(str))
|
|
}
|
|
|
|
func Base64DecodeStr(str string) (string, error) {
|
|
decoded, err := base64.StdEncoding.DecodeString(str)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(decoded), nil
|
|
}
|
|
|
|
func IsBase64(s string) bool {
|
|
// 1. Check if string is empty
|
|
if len(s) == 0 {
|
|
return false
|
|
}
|
|
|
|
// 2. Check if length is valid (must be multiple of 4)
|
|
if len(s)%4 != 0 {
|
|
return false
|
|
}
|
|
|
|
// 3. Check for valid padding
|
|
padding := strings.Count(s, "=")
|
|
if padding > 2 {
|
|
return false
|
|
}
|
|
|
|
// 4. Check if padding is at the end only
|
|
if padding > 0 && !strings.HasSuffix(s, strings.Repeat("=", padding)) {
|
|
return false
|
|
}
|
|
|
|
// 5. Check if string contains only valid base64 characters
|
|
validChars := regexp.MustCompile("^[A-Za-z0-9+/]*=*$")
|
|
if !validChars.MatchString(s) {
|
|
return false
|
|
}
|
|
|
|
// 6. Try to decode - this is the final verification
|
|
_, err := base64.StdEncoding.DecodeString(s)
|
|
return err == nil
|
|
}
|
|
|
|
var snakecaseSplitRegex = regexp.MustCompile(`[\W_]+`)
|
|
|
|
func Snakecase(str string) string {
|
|
var result strings.Builder
|
|
|
|
// split at any non word character and underscore
|
|
words := snakecaseSplitRegex.Split(str, -1)
|
|
|
|
for _, word := range words {
|
|
if word == "" {
|
|
continue
|
|
}
|
|
|
|
if result.Len() > 0 {
|
|
result.WriteString("_")
|
|
}
|
|
|
|
for i, c := range word {
|
|
if unicode.IsUpper(c) && i > 0 &&
|
|
// is not a following uppercase character
|
|
!unicode.IsUpper(rune(word[i-1])) {
|
|
result.WriteString("_")
|
|
}
|
|
|
|
result.WriteRune(c)
|
|
}
|
|
}
|
|
|
|
return strings.ToLower(result.String())
|
|
}
|
|
|
|
// randomStringWithAlphabet generates a cryptographically random string
|
|
// with the specified length and characters set.
|
|
//
|
|
// It panics if for some reason rand.Int returns a non-nil error.
|
|
func RandomStringWithAlphabet(length int, alphabet string) string {
|
|
b := make([]byte, length)
|
|
max := big.NewInt(int64(len(alphabet)))
|
|
|
|
for i := range b {
|
|
n, err := rand.Int(rand.Reader, max)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
b[i] = alphabet[n.Int64()]
|
|
}
|
|
|
|
return string(b)
|
|
}
|
|
|
|
func FileExt(str string) string {
|
|
lastDotIndex := strings.LastIndex(str, ".")
|
|
if lastDotIndex == -1 {
|
|
return ""
|
|
}
|
|
return str[lastDotIndex:]
|
|
}
|
|
|
|
func HashSHA256Hex(s string) string {
|
|
h := sha256.New()
|
|
h.Write([]byte(s))
|
|
return hex.EncodeToString(h.Sum(nil))
|
|
}
|