Files
phishingclub/backend/vendor/github.com/go-rod/rod/lib/utils/utils.go
T
Ronni Skansing f5e6f53d75 update vendor
Signed-off-by: Ronni Skansing <rskansing@gmail.com>
2026-05-24 09:57:07 +02:00

380 lines
7.5 KiB
Go

// Package utils ...
package utils
import (
"bytes"
"context"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"image"
"image/jpeg"
"image/png"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"reflect"
"regexp"
"strings"
"sync"
"text/template"
"time"
"github.com/ysmood/gson"
)
// TestEnvs for testing.
var TestEnvs = map[string]string{
"GODEBUG": "tracebackancestors=100",
}
// InContainer will be true if is inside container environment, such as docker.
var InContainer = FileExists("/.dockerenv") || FileExists("/.containerenv") ||
os.Getenv("KUBERNETES_SERVICE_HOST") != ""
// Noop does nothing.
func Noop() {}
// Logger interface.
type Logger interface {
// Same as fmt.Printf
Println(vs ...interface{})
}
// Log type for Println.
type Log func(msg ...interface{})
// Println interface.
func (l Log) Println(msg ...interface{}) {
l(msg...)
}
// LoggerQuiet does nothing.
var LoggerQuiet Logger = Log(func(_ ...interface{}) {})
// MultiLogger is similar to https://golang.org/pkg/io/#MultiWriter
func MultiLogger(list ...Logger) Log {
return Log(func(msg ...interface{}) {
for _, lg := range list {
lg.Println(msg...)
}
})
}
// Panic is the same as the built-in panic.
var Panic = func(v interface{}) { panic(v) }
// E if the last arg is error, panic it.
func E(args ...interface{}) []interface{} {
err, ok := args[len(args)-1].(error)
if ok {
Panic(err)
}
return args
}
// S Template render, the params is key-value pairs.
func S(tpl string, params ...interface{}) string {
var out bytes.Buffer
dict := map[string]interface{}{}
fnDict := template.FuncMap{}
l := len(params)
for i := 0; i < l-1; i += 2 {
k := params[i].(string) //nolint: forcetypeassert
v := params[i+1]
if reflect.TypeOf(v).Kind() == reflect.Func {
fnDict[k] = v
} else {
dict[k] = v
}
}
t := template.Must(template.New("").Funcs(fnDict).Parse(tpl))
E(t.Execute(&out, dict))
return out.String()
}
// RandString generate random string with specified string length.
func RandString(l int) string {
b := make([]byte, l)
_, _ = rand.Read(b)
return hex.EncodeToString(b)
}
// Mkdir makes dir recursively.
func Mkdir(path string) error {
return os.MkdirAll(path, 0o775)
}
// AbsolutePaths returns absolute paths of files in current working directory.
func AbsolutePaths(paths []string) []string {
absPaths := []string{}
for _, p := range paths {
absPath, err := filepath.Abs(p)
E(err)
absPaths = append(absPaths, absPath)
}
return absPaths
}
// OutputFile auto creates file if not exists, it will try to detect the data type and
// auto output binary, string or json.
func OutputFile(p string, data interface{}) error {
dir := filepath.Dir(p)
_ = Mkdir(dir)
var bin []byte
switch t := data.(type) {
case []byte:
bin = t
case string:
bin = []byte(t)
case io.Reader:
f, _ := os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o664)
_, err := io.Copy(f, t)
return err
default:
bin = MustToJSONBytes(data)
}
return os.WriteFile(p, bin, 0o664)
}
// ReadString reads file as string.
func ReadString(p string) (string, error) {
bin, err := os.ReadFile(p)
return string(bin), err
}
// All runs all actions concurrently, returns the wait function for all actions.
func All(actions ...func()) func() {
wg := &sync.WaitGroup{}
wg.Add(len(actions))
runner := func(action func()) {
defer wg.Done()
action()
}
for _, action := range actions {
go runner(action)
}
return wg.Wait
}
// IdleCounter is similar to sync.WaitGroup but it only resolves if no jobs for specified duration.
type IdleCounter struct {
lock *sync.Mutex
job int
duration time.Duration
tmr *time.Timer
}
// NewIdleCounter ...
func NewIdleCounter(d time.Duration) *IdleCounter {
tmr := time.NewTimer(time.Hour)
tmr.Stop()
return &IdleCounter{
lock: &sync.Mutex{},
duration: d,
tmr: tmr,
}
}
// Add ...
func (de *IdleCounter) Add() {
de.lock.Lock()
defer de.lock.Unlock()
de.tmr.Stop()
de.job++
}
// Done ...
func (de *IdleCounter) Done() {
de.lock.Lock()
defer de.lock.Unlock()
de.job--
if de.job == 0 {
de.reset(de.duration)
}
if de.job < 0 {
panic("all jobs are already done")
}
}
// Wait ...
func (de *IdleCounter) Wait(ctx context.Context) {
de.lock.Lock()
if de.job == 0 {
de.reset(de.duration)
}
de.lock.Unlock()
select {
case <-ctx.Done():
de.tmr.Stop()
case <-de.tmr.C:
}
}
func (de *IdleCounter) reset(d time.Duration) {
if !de.tmr.Stop() {
select {
case <-de.tmr.C:
default:
}
}
de.tmr.Reset(d)
}
var chPause = make(chan struct{})
// Pause the goroutine forever.
func Pause() {
<-chPause
}
// Dump values for debugging.
func Dump(list ...interface{}) string {
out := []string{}
for _, el := range list {
out = append(out, gson.New(el).JSON("", " "))
}
return strings.Join(out, " ")
}
// MustToJSONBytes encode data to json bytes.
func MustToJSONBytes(data interface{}) []byte {
buf := bytes.NewBuffer(nil)
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false)
E(enc.Encode(data))
b := buf.Bytes()
return b[:len(b)-1]
}
// MustToJSON encode data to json string.
func MustToJSON(data interface{}) string {
return string(MustToJSONBytes(data))
}
// FileExists checks if file exists, only for file, not for dir.
func FileExists(path string) bool {
info, err := os.Stat(path)
if err != nil {
return false
}
if info.IsDir() {
return false
}
return true
}
var regSpace = regexp.MustCompile(`\s`)
// Exec command.
func Exec(line string, rest ...string) string {
return ExecLine(true, line, rest...)
}
var execLogger = log.New(os.Stdout, "[exec] ", 0)
// ExecLine of command.
func ExecLine(std bool, line string, rest ...string) string {
args := rest
if line != "" {
args = append(regSpace.Split(line, -1), rest...)
}
execLogger.Println(FormatCLIArgs(args))
buf := bytes.NewBuffer(nil)
cmd := exec.Command(args[0], args[1:]...)
cmd.Stderr = buf
cmd.Stdout = buf
if std {
cmd.Stdin = os.Stdin
cmd.Stderr = io.MultiWriter(buf, os.Stderr)
cmd.Stdout = io.MultiWriter(buf, os.Stdout)
}
if err := cmd.Run(); err != nil {
if std {
panic(err)
}
panic(fmt.Sprintf("%v\n%v", err, buf.String()))
}
return buf.String()
}
// UseNode installs Node.js and set the bin path to PATH env var.
func UseNode(std bool) {
binPath := strings.TrimSpace(ExecLine(std, "go run github.com/ysmood/use-node@latest -p v20"))
E(os.Setenv("PATH", binPath+string(os.PathListSeparator)+os.Getenv("PATH")))
}
// FormatCLIArgs into one line string.
func FormatCLIArgs(args []string) string {
list := []string{}
for _, arg := range args {
if regSpace.MatchString(arg) {
list = append(list, fmt.Sprintf("%#v", arg))
} else {
list = append(list, arg)
}
}
return strings.Join(list, " ")
}
// EscapeGoString not using encoding like base64 or gzip because of they will
// make git diff every large for small change.
func EscapeGoString(s string) string {
return "`" + strings.ReplaceAll(s, "`", "` + \"`\" + `") + "`"
}
// CropImage by the specified box, quality is only for jpeg bin.
func CropImage(bin []byte, quality, x, y, width, height int) ([]byte, error) {
img, typ, err := image.Decode(bytes.NewBuffer(bin))
if err != nil {
return nil, err
}
cropped := bytes.NewBuffer(nil)
img = img.(interface { //nolint: forcetypeassert
SubImage(r image.Rectangle) image.Image
}).SubImage(image.Rect(
x, y, x+width, y+height,
))
switch typ {
case "png":
err = png.Encode(cropped, img)
case "jpeg":
if quality == 0 {
quality = 80
}
err = jpeg.Encode(cropped, img, &jpeg.Options{Quality: quality})
}
return cropped.Bytes(), err
}