mirror of
https://github.com/akyaiy/GoSally-mvp.git
synced 2026-01-03 20:52:25 +00:00
Compare commits
87 Commits
bf5e136dc9
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6c9e5102f | ||
| a72627d87c | |||
| 4a9719cdfb | |||
| 7de5ec5248 | |||
| e5f9105364 | |||
| ce2a23f9e6 | |||
| d56b022bf5 | |||
| ca38c10ec4 | |||
| 13dbd00bb7 | |||
| e7289dc9be | |||
| 5394178abc | |||
| 981551e944 | |||
| 27446adf3f | |||
| 2f071c25b2 | |||
| d23fd32e84 | |||
| 86d35a9ede | |||
| c77d51a95c | |||
| 3cbea14e84 | |||
| 6e59af1662 | |||
| 8684d178e0 | |||
| 945ab6c9cf | |||
| 520901c331 | |||
| 9a274250cd | |||
| 6d49d83ea7 | |||
| fb04b3bc46 | |||
| a60b75a4c0 | |||
| 041fda8522 | |||
| 6508f03d08 | |||
| 93cf53025c | |||
| 83912b6c28 | |||
| 6ed5a7f9e0 | |||
| 2f78e9367c | |||
| ac074ce0ff | |||
| 8bdf9197d6 | |||
| 4db8fa2360 | |||
| 2a48927a08 | |||
| 58027bb988 | |||
| 30a87fdb4c | |||
| 5cdfb2a543 | |||
| 08e96aa32a | |||
| 3b8390a0c8 | |||
| b6ad0f82a0 | |||
| 7009828e79 | |||
| 45e541ac00 | |||
| a5a7354061 | |||
| 20bb90e77a | |||
| 148ca53538 | |||
| 2951fd2da9 | |||
| f411637520 | |||
| 75ee6e10aa | |||
| cfa7724b68 | |||
| f44e89b0de | |||
| 23ed707029 | |||
| 299fd59e19 | |||
| b601962354 | |||
| 38f784b850 | |||
| 6d2bf5cdd2 | |||
| 166c8470d4 | |||
| 64510a5307 | |||
| b454f4de8d | |||
| c161639766 | |||
| dd336a7d9a | |||
| ab37ecb7f7 | |||
| bd02f079ab | |||
| b97febc16e | |||
| 149cfc0a17 | |||
| 00276dc817 | |||
| ec2ef34f23 | |||
| aebc3d2e9b | |||
| 22ff90ca56 | |||
| f3c4b9e9b1 | |||
| 98d2443679 | |||
| c61bc841e6 | |||
| 74f166e6cf | |||
|
|
81359c036c | ||
| 92c89996f5 | |||
| 1c73d3f87a | |||
| e35972b8ad | |||
| 0344d58ad4 | |||
| cf7bd1ceec | |||
| c3540bfbe1 | |||
| bd54628b5c | |||
| b103736a9d | |||
| 7eeedf0b31 | |||
| 1675001f24 | |||
| e01ecdf1db | |||
| febee7cac5 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -3,7 +3,10 @@ bin/
|
||||
cert/
|
||||
tmp/
|
||||
.meta/
|
||||
db/
|
||||
|
||||
com/test.lua
|
||||
|
||||
.vscode
|
||||
Taskfile.yml
|
||||
|
||||
config.yaml
|
||||
13
Makefile
13
Makefile
@@ -4,7 +4,7 @@ GOPATH := $(shell go env GOPATH)
|
||||
export CONFIG_PATH := ./config.yaml
|
||||
export NODE_PATH := $(shell pwd)
|
||||
|
||||
LDFLAGS := -X 'github.com/akyaiy/GoSally-mvp/core/config.NodeVersion=v0.0.1-dev'
|
||||
LDFLAGS := -X 'github.com/akyaiy/GoSally-mvp/internal/engine/config.NodeVersion=v0.0.1-dev'
|
||||
CGO_CFLAGS := -I/usr/local/include
|
||||
CGO_LDFLAGS := -L/usr/local/lib -llua5.1 -lm -ldl
|
||||
.PHONY: all build run runq test fmt vet lint check clean
|
||||
@@ -30,7 +30,14 @@ build:
|
||||
@# @echo "CGO_CFLAGS is: '$(CGO_CFLAGS)'"
|
||||
@# @echo "CGO_LDFLAGS is: '$(CGO_LDFLAGS)'"
|
||||
@# CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)"
|
||||
go build -ldflags "$(LDFLAGS)" -o $(BIN_DIR)/$(APP_NAME) ./
|
||||
@go build -trimpath -ldflags "-w -s $(LDFLAGS)" -o $(BIN_DIR)/$(APP_NAME) ./
|
||||
@if ! command -v upx >/dev/null 2>&1; then \
|
||||
echo "upx not found, skipping compression."; \
|
||||
elif upx -t $(BIN_DIR)/$(APP_NAME) >/dev/null 2>&1; then \
|
||||
echo "$(BIN_DIR)/$(APP_NAME) already compressed, skipping."; \
|
||||
else \
|
||||
upx $(BIN_DIR)/$(APP_NAME) >/dev/null 2>&1 || true; \
|
||||
fi
|
||||
|
||||
run: build
|
||||
@echo "Running!"
|
||||
@@ -50,8 +57,10 @@ test:
|
||||
fmt:
|
||||
@go fmt ./internal/./...
|
||||
@go fmt ./cmd/./...
|
||||
@go fmt ./hooks/./...
|
||||
@$(GOPATH)/bin/goimports -w ./internal/
|
||||
@$(GOPATH)/bin/goimports -w ./cmd/
|
||||
@$(GOPATH)/bin/goimports -w ./hooks/
|
||||
|
||||
vet:
|
||||
@go vet ./...
|
||||
|
||||
14
cmd/root.go
14
cmd/root.go
@@ -5,18 +5,18 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/hooks"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/colors"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/logs"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var compositor *config.Compositor = config.NewCompositor()
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "node",
|
||||
Short: "Go Sally node",
|
||||
Long: "Main node runner for Go Sally",
|
||||
Long: `
|
||||
GoSally is an http server that handles jsonrpc-2.0 requests by calling methods as lua
|
||||
scripts in a given directory. For more information, visit: https://gosally.oblat.lv/`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
_ = cmd.Help()
|
||||
},
|
||||
@@ -24,9 +24,9 @@ var rootCmd = &cobra.Command{
|
||||
|
||||
func Execute() {
|
||||
log.SetOutput(os.Stdout)
|
||||
log.SetPrefix(logs.SetBrightBlack(fmt.Sprintf("(%s) ", corestate.StageNotReady)))
|
||||
log.SetPrefix(colors.SetBrightBlack(fmt.Sprintf("(%s) ", corestate.StageNotReady)))
|
||||
log.SetFlags(log.Ldate | log.Ltime)
|
||||
compositor.LoadCMDLine(rootCmd)
|
||||
hooks.Compositor.LoadCMDLine(rootCmd)
|
||||
_ = rootCmd.Execute()
|
||||
// if err := rootCmd.Execute(); err != nil {
|
||||
// log.Fatalf("Unexpected error: %s", err.Error())
|
||||
|
||||
334
cmd/run.go
334
cmd/run.go
@@ -1,337 +1,17 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/run_manager"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/update"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/logs"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/gateway"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/sv1"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/akyaiy/GoSally-mvp/hooks"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/netutil"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
func contains(slice []string, item string) bool {
|
||||
for _, v := range slice {
|
||||
if v == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var runCmd = &cobra.Command{
|
||||
Use: "run",
|
||||
Short: "Run node normally",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
nodeApp := app.New()
|
||||
|
||||
nodeApp.InitialHooks(
|
||||
func(cs *corestate.CoreState, x *app.AppX) {
|
||||
x.Config = compositor
|
||||
x.Log.SetOutput(os.Stdout)
|
||||
x.Log.SetPrefix(logs.SetBrightBlack(fmt.Sprintf("(%s) ", cs.Stage)))
|
||||
x.Log.SetFlags(log.Ldate | log.Ltime)
|
||||
},
|
||||
|
||||
// First stage: pre-init
|
||||
func(cs *corestate.CoreState, x *app.AppX) {
|
||||
*cs = *corestate.NewCorestate(&corestate.CoreState{
|
||||
UUID32DirName: "uuid",
|
||||
NodeBinName: filepath.Base(os.Args[0]),
|
||||
NodeVersion: config.NodeVersion,
|
||||
MetaDir: "./.meta",
|
||||
Stage: corestate.StagePreInit,
|
||||
StartTimestampUnix: time.Now().Unix(),
|
||||
})
|
||||
},
|
||||
|
||||
func(cs *corestate.CoreState, x *app.AppX) {
|
||||
x.Log.SetPrefix(logs.SetBlue(fmt.Sprintf("(%s) ", cs.Stage)))
|
||||
|
||||
if err := x.Config.LoadEnv(); err != nil {
|
||||
x.Log.Fatalf("env load error: %s", err)
|
||||
}
|
||||
cs.NodePath = x.Config.Env.NodePath
|
||||
|
||||
if cfgPath := x.Config.CMDLine.Run.ConfigPath; cfgPath != "" {
|
||||
x.Config.Env.ConfigPath = cfgPath
|
||||
}
|
||||
if err := x.Config.LoadConf(x.Config.Env.ConfigPath); err != nil {
|
||||
x.Log.Fatalf("conf load error: %s", err)
|
||||
}
|
||||
},
|
||||
|
||||
func(cs *corestate.CoreState, x *app.AppX) {
|
||||
uuid32, err := corestate.GetNodeUUID(filepath.Join(cs.MetaDir, "uuid"))
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if err := corestate.SetNodeUUID(filepath.Join(cs.NodePath, cs.MetaDir, cs.UUID32DirName)); err != nil {
|
||||
x.Log.Fatalf("Cannod generate node uuid: %s", err.Error())
|
||||
}
|
||||
uuid32, err = corestate.GetNodeUUID(filepath.Join(cs.MetaDir, "uuid"))
|
||||
if err != nil {
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
x.Log.Fatalf("uuid load error: %s", err)
|
||||
}
|
||||
cs.UUID32 = uuid32
|
||||
},
|
||||
|
||||
func(cs *corestate.CoreState, x *app.AppX) {
|
||||
if x.Config.Env.ParentStagePID != os.Getpid() {
|
||||
if !contains(x.Config.Conf.DisableWarnings, "--WNonStdTmpDir") && os.TempDir() != "/tmp" {
|
||||
x.Log.Printf("%s: %s", logs.PrintWarn(), "Non-standard value specified for temporary directory")
|
||||
}
|
||||
// still pre-init stage
|
||||
runDir, err := run_manager.Create(cs.UUID32)
|
||||
if err != nil {
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
cs.RunDir = runDir
|
||||
input, err := os.Open(os.Args[0])
|
||||
if err != nil {
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
if err := run_manager.Set(cs.NodeBinName); err != nil {
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
fmgr := run_manager.File(cs.NodeBinName)
|
||||
output, err := fmgr.Open()
|
||||
if err != nil {
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
|
||||
if _, err := io.Copy(output, input); err != nil {
|
||||
fmgr.Close()
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
if err := os.Chmod(filepath.Join(cs.RunDir, cs.NodeBinName), 0755); err != nil {
|
||||
fmgr.Close()
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
input.Close()
|
||||
fmgr.Close()
|
||||
runArgs := os.Args
|
||||
runArgs[0] = filepath.Join(cs.RunDir, cs.NodeBinName)
|
||||
|
||||
// prepare environ
|
||||
env := utils.SetEviron(os.Environ(), fmt.Sprintf("GS_PARENT_PID=%d", os.Getpid()))
|
||||
|
||||
if err := syscall.Exec(runArgs[0], runArgs, env); err != nil {
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
}
|
||||
x.Log.Printf("Node uuid is %s", cs.UUID32)
|
||||
},
|
||||
|
||||
// post-init stage
|
||||
func(cs *corestate.CoreState, x *app.AppX) {
|
||||
cs.Stage = corestate.StagePostInit
|
||||
x.Log.SetPrefix(logs.SetYellow(fmt.Sprintf("(%s) ", cs.Stage)))
|
||||
|
||||
cs.RunDir = run_manager.Toggle()
|
||||
exist, err := utils.ExistsMatchingDirs(filepath.Join(os.TempDir(), fmt.Sprintf("/*-%s-%s", cs.UUID32, "gosally-runtime")), cs.RunDir)
|
||||
if err != nil {
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
if exist {
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unable to continue node operation: A node with the same identifier was found in the runtime environment")
|
||||
}
|
||||
|
||||
if err := run_manager.Set("run.lock"); err != nil {
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
lockPath, err := run_manager.Get("run.lock")
|
||||
if err != nil {
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
lockFile := ini.Empty()
|
||||
secRun, err := lockFile.NewSection("runtime")
|
||||
if err != nil {
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
secRun.Key("pid").SetValue(fmt.Sprintf("%d/%d", os.Getpid(), x.Config.Env.ParentStagePID))
|
||||
secRun.Key("version").SetValue(cs.NodeVersion)
|
||||
secRun.Key("uuid").SetValue(cs.UUID32)
|
||||
secRun.Key("timestamp").SetValue(time.Unix(cs.StartTimestampUnix, 0).Format("2006-01-02/15:04:05 MST"))
|
||||
secRun.Key("timestamp-unix").SetValue(fmt.Sprintf("%d", cs.StartTimestampUnix))
|
||||
|
||||
err = lockFile.SaveTo(lockPath)
|
||||
if err != nil {
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
},
|
||||
|
||||
func(cs *corestate.CoreState, x *app.AppX) {
|
||||
cs.Stage = corestate.StageReady
|
||||
x.Log.SetPrefix(logs.SetGreen(fmt.Sprintf("(%s) ", cs.Stage)))
|
||||
|
||||
x.SLog = new(slog.Logger)
|
||||
newSlog, err := logs.SetupLogger(x.Config.Conf.Log)
|
||||
if err != nil {
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
*x.SLog = *newSlog
|
||||
},
|
||||
)
|
||||
|
||||
nodeApp.Run(func(ctx context.Context, cs *corestate.CoreState, x *app.AppX) error {
|
||||
ctxMain, cancelMain := context.WithCancel(ctx)
|
||||
runLockFile := run_manager.File("run.lock")
|
||||
_, err := runLockFile.Open()
|
||||
if err != nil {
|
||||
x.Log.Fatalf("cannot open run.lock: %s", err)
|
||||
}
|
||||
|
||||
_, err = runLockFile.Watch(ctxMain, func() {
|
||||
x.Log.Printf("run.lock was touched")
|
||||
_ = run_manager.Clean()
|
||||
cancelMain()
|
||||
})
|
||||
if err != nil {
|
||||
x.Log.Printf("watch error: %s", err)
|
||||
}
|
||||
|
||||
serverv1 := sv1.InitV1Server(&sv1.HandlerV1InitStruct{
|
||||
Log: *x.SLog,
|
||||
Config: x.Config.Conf,
|
||||
AllowedCmd: regexp.MustCompile(`^[a-zA-Z0-9]+$`),
|
||||
ListAllowedCmd: regexp.MustCompile(`^[a-zA-Z0-9_-]+$`),
|
||||
Ver: "v1",
|
||||
})
|
||||
|
||||
s := gateway.InitGateway(&gateway.GatewayServerInit{
|
||||
Log: x.SLog,
|
||||
Config: x.Config.Conf,
|
||||
}, serverv1)
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.Use(cors.Handler(cors.Options{
|
||||
AllowedOrigins: []string{"*"},
|
||||
AllowedMethods: []string{"GET", "POST", "OPTIONS"},
|
||||
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
|
||||
AllowCredentials: true,
|
||||
MaxAge: 300,
|
||||
}))
|
||||
r.HandleFunc(config.ComDirRoute, s.Handle)
|
||||
r.Route("/favicon.ico", func(r chi.Router) {
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
})
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: x.Config.Conf.HTTPServer.Address,
|
||||
Handler: r,
|
||||
ErrorLog: log.New(&logs.SlogWriter{
|
||||
Logger: x.SLog,
|
||||
Level: logs.GlobalLevel,
|
||||
}, "", 0),
|
||||
}
|
||||
go func() {
|
||||
if x.Config.Conf.TLS.TlsEnabled {
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port))
|
||||
if err != nil {
|
||||
x.Log.Printf("%s: Failed to start TLS listener: %s", logs.PrintError(), err.Error())
|
||||
cancelMain()
|
||||
return
|
||||
}
|
||||
x.Log.Printf("Serving on %s port %s with TLS... (https://%s%s)", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port, fmt.Sprintf("%s:%s", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port), config.ComDirRoute)
|
||||
limitedListener := netutil.LimitListener(listener, 100)
|
||||
if err := srv.ServeTLS(limitedListener, x.Config.Conf.TLS.CertFile, x.Config.Conf.TLS.KeyFile); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
x.Log.Printf("%s: Failed to start HTTPS server: %s", logs.PrintError(), err.Error())
|
||||
cancelMain()
|
||||
}
|
||||
} else {
|
||||
x.Log.Printf("Serving on %s port %s... (http://%s%s)", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port, fmt.Sprintf("%s:%s", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port), config.ComDirRoute)
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port))
|
||||
if err != nil {
|
||||
x.Log.Printf("%s: Failed to start listener: %s", logs.PrintError(), err.Error())
|
||||
cancelMain()
|
||||
return
|
||||
}
|
||||
limitedListener := netutil.LimitListener(listener, 100)
|
||||
if err := srv.Serve(limitedListener); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
x.Log.Printf("%s: Failed to start HTTP server: %s", logs.PrintError(), err.Error())
|
||||
cancelMain()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if x.Config.Conf.Updates.UpdatesEnabled {
|
||||
go func() {
|
||||
x.Updated = update.NewUpdater(ctxMain, x.Log, x.Config.Conf, x.Config.Env)
|
||||
x.Updated.Shutdownfunc(cancelMain)
|
||||
for {
|
||||
isNewUpdate, err := x.Updated.CkeckUpdates()
|
||||
if err != nil {
|
||||
x.Log.Printf("Failed to check for updates: %s", err.Error())
|
||||
}
|
||||
if isNewUpdate {
|
||||
if err := x.Updated.Update(); err != nil {
|
||||
x.Log.Printf("Failed to update: %s", err.Error())
|
||||
} else {
|
||||
x.Log.Printf("Update completed successfully")
|
||||
}
|
||||
}
|
||||
time.Sleep(x.Config.Conf.Updates.CheckInterval)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
<-ctxMain.Done()
|
||||
if err := srv.Shutdown(ctxMain); err != nil {
|
||||
x.Log.Printf("%s: Failed to stop the server gracefully: %s", logs.PrintError(), err.Error())
|
||||
} else {
|
||||
x.Log.Printf("Server stopped gracefully")
|
||||
}
|
||||
|
||||
x.Log.Println("Cleaning up...")
|
||||
|
||||
if err := run_manager.Clean(); err != nil {
|
||||
x.Log.Printf("%s: Cleanup error: %s", logs.PrintError(), err.Error())
|
||||
}
|
||||
x.Log.Println("bye!")
|
||||
|
||||
return nil
|
||||
})
|
||||
},
|
||||
Use: "run",
|
||||
Aliases: []string{"r"},
|
||||
Short: "Run node normally",
|
||||
Long: `
|
||||
"run" starts the node with settings depending on the configuration file`,
|
||||
Run: hooks.Run,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
24
cmd/version.go
Normal file
24
cmd/version.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var verCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Aliases: []string{"ver", "v"},
|
||||
Short: "Return node version",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("GoSally node: %s\n", config.NodeVersion)
|
||||
fmt.Printf("Go version: %s\n", runtime.Version())
|
||||
fmt.Printf("Go OS/Arch: %s/%s\n", runtime.GOOS, runtime.GOARCH)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(verCmd)
|
||||
}
|
||||
66
com/DB/Put.lua
Normal file
66
com/DB/Put.lua
Normal file
@@ -0,0 +1,66 @@
|
||||
---@diagnostic disable: redefined-local
|
||||
local db = require("internal.database-sqlite").connect("db/test.db", {log = true})
|
||||
local log = require("internal.log")
|
||||
local session = require("internal.session")
|
||||
|
||||
if not (session.request.params.name and session.request.params.email) then
|
||||
session.response.error = {
|
||||
code = -32602,
|
||||
message = "Name and email are required"
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
local existing, err = db:query("SELECT 1 FROM users WHERE email = ? LIMIT 1", {
|
||||
session.request.params.email
|
||||
})
|
||||
if err ~= nil then
|
||||
session.response.error = {
|
||||
code = -32603,
|
||||
message = "Database check failed: "..tostring(err)
|
||||
}
|
||||
log.error("Email check failed: "..tostring(err))
|
||||
return
|
||||
end
|
||||
|
||||
if existing and #existing > 0 then
|
||||
session.response.error = {
|
||||
code = -32604,
|
||||
message = "Email already exists"
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
local ctx, err = db:exec(
|
||||
"INSERT INTO users (name, email) VALUES (?, ?)",
|
||||
{
|
||||
session.request.params.name,
|
||||
session.request.params.email
|
||||
}
|
||||
)
|
||||
if err ~= nil then
|
||||
session.response.error = {
|
||||
code = -32605,
|
||||
message = "Insert failed: "..tostring(err)
|
||||
}
|
||||
log.error("Insert failed: "..tostring(err))
|
||||
return
|
||||
end
|
||||
|
||||
local res, err = ctx:wait()
|
||||
if err ~= nil then
|
||||
session.response.error = {
|
||||
code = -32606,
|
||||
message = "Insert confirmation failed: "..tostring(err)
|
||||
}
|
||||
log.error("Insert confirmation failed: "..tostring(err))
|
||||
return
|
||||
end
|
||||
|
||||
session.response.result = {
|
||||
success = true,
|
||||
rows_affected = res,
|
||||
message = "User created successfully"
|
||||
}
|
||||
|
||||
db:close()
|
||||
55
com/List.lua
Normal file
55
com/List.lua
Normal file
@@ -0,0 +1,55 @@
|
||||
-- com/List.lua
|
||||
|
||||
local session = require("internal.session")
|
||||
|
||||
if session.request and session.request.params and session.request.params.about then
|
||||
session.response.result = {
|
||||
description = "Returns a list of available methods",
|
||||
params = {
|
||||
layer = "select which layer list to display"
|
||||
}
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
local function isValidName(name)
|
||||
return name:match("^[%w]+$") ~= nil
|
||||
end
|
||||
|
||||
local function scanDirectory(basePath, targetPath)
|
||||
local res = {}
|
||||
local fullPath = basePath.."/"..targetPath
|
||||
local handle = io.popen('find "'..fullPath..'" -type f -name "*.lua" 2>/dev/null')
|
||||
|
||||
if handle then
|
||||
for filePath in handle:lines() do
|
||||
local parts = {}
|
||||
for part in filePath:gsub(".lua$", ""):gmatch("[^/]+") do
|
||||
table.insert(parts, part)
|
||||
end
|
||||
|
||||
local allValid = true
|
||||
for _, part in ipairs(parts) do
|
||||
if not isValidName(part) then
|
||||
allValid = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if allValid then
|
||||
local relPath = filePath:gsub("^"..basePath.."/", ""):gsub(".lua$", ""):gsub("/", ">")
|
||||
table.insert(res, relPath)
|
||||
end
|
||||
end
|
||||
handle:close()
|
||||
end
|
||||
|
||||
return #res > 0 and res or nil
|
||||
end
|
||||
|
||||
local basePath = "com"
|
||||
local layer = session.request and session.request.params.layer and session.request.params.layer:gsub(">", "/") or nil
|
||||
|
||||
session.response.result = {
|
||||
answer = layer and scanDirectory(basePath, layer) or scanDirectory(basePath, "")
|
||||
}
|
||||
29
com/Net/GetExpl.lua
Normal file
29
com/Net/GetExpl.lua
Normal file
@@ -0,0 +1,29 @@
|
||||
local session = require("internal.session")
|
||||
local net = require("internal.net")
|
||||
|
||||
local reqAddr
|
||||
local logReq = true
|
||||
|
||||
if session.request.params and session.request.params.url then
|
||||
reqAddr = session.request.params.url
|
||||
else
|
||||
session.response.error = {
|
||||
code = -32602,
|
||||
message = "no url provided"
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
local resp = net.http.get_request(logReq, reqAddr)
|
||||
if resp then
|
||||
session.response.result.answer = {
|
||||
status = resp.status,
|
||||
body = resp.body
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
session.response.error = {
|
||||
data = "error while requesting"
|
||||
}
|
||||
|
||||
35
com/Net/PostExpl.lua
Normal file
35
com/Net/PostExpl.lua
Normal file
@@ -0,0 +1,35 @@
|
||||
local session = require("internal.session")
|
||||
local net = require("internal.net")
|
||||
local log = require("internal.log")
|
||||
|
||||
local reqAddr
|
||||
local logReq = true
|
||||
local payload
|
||||
|
||||
log.debug(session.request.params)
|
||||
|
||||
if not (session.request.params and session.request.params.url) then
|
||||
session.response.error = {
|
||||
code = -32602,
|
||||
message = "no url or payload provided"
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
|
||||
reqAddr = session.request.params.url
|
||||
payload = session.request.params.payload
|
||||
|
||||
local resp = net.http.post_request(logReq, reqAddr, "application/json", payload)
|
||||
if resp then
|
||||
session.response.result.answer = {
|
||||
status = resp.status,
|
||||
body = resp.body
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
session.response.error = {
|
||||
data = "error while requesting"
|
||||
}
|
||||
22
com/Utils/Echo.lua
Normal file
22
com/Utils/Echo.lua
Normal file
@@ -0,0 +1,22 @@
|
||||
if In.Params and In.Params.about then
|
||||
Out.Result = {
|
||||
description = "Echo of the message",
|
||||
params = {
|
||||
msg = "just message"
|
||||
}
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
local function validate()
|
||||
if not In.Params.msg or In.Params.msg == "" then
|
||||
Out.Error = {
|
||||
message = "there must be a msg parameter"
|
||||
}
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
validate()
|
||||
Out.Result.answer = In.Params.msg
|
||||
8
com/Utils/Ping.lua
Normal file
8
com/Utils/Ping.lua
Normal file
@@ -0,0 +1,8 @@
|
||||
if In.Params and In.Params.about then
|
||||
Out.Result = {
|
||||
description = "Just ping"
|
||||
}
|
||||
return
|
||||
end
|
||||
|
||||
Out.Result.answer = "pong"
|
||||
@@ -1,11 +1,54 @@
|
||||
---@alias AnyTable table<string, any>
|
||||
--@diagnostic disable: missing-fields, missing-return
|
||||
|
||||
---@type AnyTable
|
||||
In = {
|
||||
Params = {},
|
||||
}
|
||||
---@alias Any any
|
||||
---@alias AnyTable table<string, Any>
|
||||
|
||||
---@type AnyTable
|
||||
Out = {
|
||||
Result = {},
|
||||
}
|
||||
--- Global session module interface
|
||||
---@class SessionIn
|
||||
---@field params AnyTable Request parameters
|
||||
|
||||
---@class SessionOut
|
||||
---@field result Any|string? Result payload (table or primitive)
|
||||
---@field error { code: integer, message: string, data: Any }? Optional error info
|
||||
|
||||
---@class SessionModule
|
||||
---@field request SessionIn Input context (read-only)
|
||||
---@field response SessionOut Output context (write results/errors)
|
||||
|
||||
--- Global log module interface
|
||||
---@class LogModule
|
||||
---@field info fun(msg: string) Log informational message
|
||||
---@field debug fun(msg: string) Log debug message
|
||||
---@field error fun(msg: string) Log error message
|
||||
---@field warn fun(msg: string) Log warning message
|
||||
---@field event fun(msg: string) Log event (generic)
|
||||
---@field event_error fun(msg: string) Log event error
|
||||
---@field event_warn fun(msg: string) Log event warning
|
||||
|
||||
--- Global net module interface
|
||||
---@class HttpResponse
|
||||
---@field status integer HTTP status code
|
||||
---@field status_text string HTTP status text
|
||||
---@field body string Response body
|
||||
---@field content_length integer Content length
|
||||
---@field headers AnyTable Map of headers
|
||||
|
||||
---@class HttpModule
|
||||
---@field get fun(log: boolean, url: string): HttpResponse, string? Perform GET
|
||||
---@field post fun(log: boolean, url: string, content_type: string, payload: string): HttpResponse, string? Perform POST
|
||||
|
||||
---@class NetModule
|
||||
---@field http HttpModule HTTP client functions
|
||||
|
||||
--- Global variables declaration
|
||||
---@global
|
||||
---@type SessionModule
|
||||
_G.session = session or {}
|
||||
|
||||
---@global
|
||||
---@type LogModule
|
||||
_G.log = log or {}
|
||||
|
||||
---@global
|
||||
---@type NetModule
|
||||
_G.net = net or {}
|
||||
13
com/echo.lua
13
com/echo.lua
@@ -1,13 +0,0 @@
|
||||
--- #description = "Echoes back the message."
|
||||
--- #args
|
||||
--- msg = the message
|
||||
|
||||
if not In.Params.msg or In.Params.msg == "" then
|
||||
Out.Result.status = Status.error
|
||||
Out.Result.error = "Missing parameter: msg"
|
||||
return
|
||||
end
|
||||
|
||||
Out.Result.status = Status.ok
|
||||
Out.Result.answer = In.Params.msg
|
||||
return
|
||||
@@ -1,21 +1,30 @@
|
||||
mode: "prod"
|
||||
node:
|
||||
mode: dev
|
||||
name: "My gosally node"
|
||||
show_config: true
|
||||
com_dir: "%path%/com"
|
||||
|
||||
http_server:
|
||||
address: "0.0.0.0:8080"
|
||||
api:
|
||||
latest-version: v1
|
||||
layers:
|
||||
- b1
|
||||
- s2
|
||||
address: "0.0.0.0"
|
||||
port: "8080"
|
||||
session_ttl: 5s
|
||||
timeout: 3s
|
||||
idle_timeout: 30s
|
||||
|
||||
tls:
|
||||
enabled: false
|
||||
cert_file: "./cert/fullchain.pem"
|
||||
key_file: "./cert/privkey.pem"
|
||||
|
||||
com_dir: "com/"
|
||||
enabled: true
|
||||
cert_file: "%path%/cert/fullchain.pem"
|
||||
key_file: "%path%/cert/privkey.pem"
|
||||
|
||||
updates:
|
||||
enabled: true
|
||||
enabled: false
|
||||
check-interval: 1h
|
||||
repository_url: "https://repo.serve.lv/raw/go-sally"
|
||||
repository_url: "https://repo.serve.lv/raw/go-sally"
|
||||
|
||||
log:
|
||||
json_format: false
|
||||
level: "debug"
|
||||
|
||||
disable_warnings:
|
||||
- --WNonStdTmpDir
|
||||
- --WUndefLogLevel
|
||||
15
go.mod
15
go.mod
@@ -4,28 +4,37 @@ go 1.24.4
|
||||
|
||||
require (
|
||||
github.com/go-chi/chi/v5 v5.2.2
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/spf13/viper v1.20.1
|
||||
github.com/yuin/gopher-lua v1.1.1
|
||||
golang.org/x/net v0.42.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
modernc.org/sqlite v1.38.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/sagikazarmark/locafero v0.9.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/sagikazarmark/locafero v0.10.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
github.com/spf13/afero v1.14.0 // indirect
|
||||
github.com/spf13/cast v1.9.2 // indirect
|
||||
github.com/spf13/pflag v1.0.7 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
modernc.org/libc v1.66.6 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
60
go.sum
60
go.sum
@@ -1,6 +1,8 @@
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
@@ -9,36 +11,43 @@ github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
|
||||
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
|
||||
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
|
||||
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/sagikazarmark/locafero v0.10.0 h1:FM8Cv6j2KqIhM2ZK7HZjm4mpj9NBktLgowT1aN9q5Cc=
|
||||
github.com/sagikazarmark/locafero v0.10.0/go.mod h1:Ieo3EUsjifvQu4NZwV5sPd4dwvu0OCgEQV7vjc9yDjw=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
||||
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
||||
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
||||
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
@@ -50,14 +59,21 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
|
||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
|
||||
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -67,3 +83,29 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/cc/v4 v4.26.3 h1:yEN8dzrkRFnn4PUUKXLYIqVf2PJYAEjMTFjO3BDGc3I=
|
||||
modernc.org/cc/v4 v4.26.3/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
|
||||
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
|
||||
modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM=
|
||||
modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.66.6 h1:RyQpwAhM/19nXD8y3iejM/AjmKwY2TjxZTlUWTsWw2U=
|
||||
modernc.org/libc v1.66.6/go.mod h1:j8z0EYAuumoMQ3+cWXtmw6m+LYn3qm8dcZDFtFTSq+M=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek=
|
||||
modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
|
||||
403
hooks/initial.go
Normal file
403
hooks/initial.go
Normal file
@@ -0,0 +1,403 @@
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/colors"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/run_manager"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/logs"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
var Compositor *config.Compositor = config.NewCompositor()
|
||||
|
||||
func InitGlobalLoggerHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||
x.Config = Compositor
|
||||
x.Log.SetOutput(os.Stdout)
|
||||
x.Log.SetPrefix(colors.SetBrightBlack(fmt.Sprintf("(%s) ", cs.Stage)))
|
||||
x.Log.SetFlags(log.Ldate | log.Ltime)
|
||||
}
|
||||
|
||||
// First stage: pre-init
|
||||
func InitCorestateHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||
*cs = *corestate.NewCorestate(&corestate.CoreState{
|
||||
UUID32DirName: "uuid",
|
||||
NodeBinName: filepath.Base(os.Args[0]),
|
||||
NodeVersion: config.NodeVersion,
|
||||
MetaDir: "./.meta",
|
||||
Stage: corestate.StagePreInit,
|
||||
StartTimestampUnix: time.Now().Unix(),
|
||||
})
|
||||
}
|
||||
|
||||
func InitConfigLoadHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||
x.Log.SetPrefix(colors.SetYellow(fmt.Sprintf("(%s) ", cs.Stage)))
|
||||
|
||||
if err := x.Config.LoadEnv(); err != nil {
|
||||
x.Log.Fatalf("env load error: %s", err)
|
||||
}
|
||||
cs.NodePath = *x.Config.Env.NodePath
|
||||
|
||||
if cfgPath := x.Config.CMDLine.Run.ConfigPath; cfgPath != "" {
|
||||
x.Config.Env.ConfigPath = &cfgPath
|
||||
}
|
||||
if err := x.Config.LoadConf(*x.Config.Env.ConfigPath); err != nil {
|
||||
x.Log.Fatalf("conf load error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func InitUUUDHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||
uuid32, err := corestate.GetNodeUUID(filepath.Join(cs.MetaDir, "uuid"))
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if err := corestate.SetNodeUUID(filepath.Join(cs.NodePath, cs.MetaDir, cs.UUID32DirName)); err != nil {
|
||||
x.Log.Fatalf("Cannod generate node uuid: %s", err.Error())
|
||||
}
|
||||
uuid32, err = corestate.GetNodeUUID(filepath.Join(cs.MetaDir, "uuid"))
|
||||
if err != nil {
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
x.Log.Fatalf("uuid load error: %s", err)
|
||||
}
|
||||
cs.UUID32 = uuid32
|
||||
}
|
||||
|
||||
func InitRuntimeHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||
if *x.Config.Env.ParentStagePID != os.Getpid() {
|
||||
// still pre-init stage
|
||||
runDir, err := run_manager.Create(cs.UUID32)
|
||||
if err != nil {
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
cs.RunDir = runDir
|
||||
input, err := os.Open(os.Args[0])
|
||||
if err != nil {
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
if err := run_manager.Set(cs.NodeBinName); err != nil {
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
fmgr := run_manager.File(cs.NodeBinName)
|
||||
output, err := fmgr.Open()
|
||||
if err != nil {
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
|
||||
if _, err := io.Copy(output, input); err != nil {
|
||||
fmgr.Close()
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
if err := os.Chmod(filepath.Join(cs.RunDir, cs.NodeBinName), 0755); err != nil {
|
||||
fmgr.Close()
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
input.Close()
|
||||
fmgr.Close()
|
||||
runArgs := os.Args
|
||||
runArgs[0] = filepath.Join(cs.RunDir, cs.NodeBinName)
|
||||
|
||||
// prepare environ
|
||||
env := utils.SetEviron(os.Environ(), fmt.Sprintf("GS_PARENT_PID=%d", os.Getpid()))
|
||||
|
||||
if err := syscall.Exec(runArgs[0], runArgs, env); err != nil {
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
}
|
||||
x.Log.Printf("Node uuid is %s", cs.UUID32)
|
||||
}
|
||||
|
||||
// post-init stage
|
||||
func InitRunlockHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||
NodeApp.Fallback(func(ctx context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||
x.Log.Println("Cleaning up...")
|
||||
|
||||
if err := run_manager.Clean(); err != nil {
|
||||
x.Log.Printf("%s: Cleanup error: %s", colors.PrintError(), err.Error())
|
||||
}
|
||||
x.Log.Println("bye!")
|
||||
})
|
||||
|
||||
cs.Stage = corestate.StagePostInit
|
||||
x.Log.SetPrefix(colors.SetBlue(fmt.Sprintf("(%s) ", cs.Stage)))
|
||||
|
||||
cs.RunDir = run_manager.Toggle()
|
||||
exist, err := utils.ExistsMatchingDirs(filepath.Join(os.TempDir(), fmt.Sprintf("/*-%s-%s", cs.UUID32, "gosally-runtime")), cs.RunDir)
|
||||
if err != nil {
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
if exist {
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unable to continue node operation: A node with the same identifier was found in the runtime environment")
|
||||
}
|
||||
|
||||
if err := run_manager.Set("run.lock"); err != nil {
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
lockPath, err := run_manager.Get("run.lock")
|
||||
if err != nil {
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
lockFile := ini.Empty()
|
||||
secRun, err := lockFile.NewSection("runtime")
|
||||
if err != nil {
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
secRun.Key("pid").SetValue(fmt.Sprintf("%d/%d", os.Getpid(), x.Config.Env.ParentStagePID))
|
||||
secRun.Key("version").SetValue(cs.NodeVersion)
|
||||
secRun.Key("uuid").SetValue(cs.UUID32)
|
||||
secRun.Key("timestamp").SetValue(time.Unix(cs.StartTimestampUnix, 0).Format("2006-01-02/15:04:05 MST"))
|
||||
secRun.Key("timestamp-unix").SetValue(fmt.Sprintf("%d", cs.StartTimestampUnix))
|
||||
|
||||
err = lockFile.SaveTo(lockPath)
|
||||
if err != nil {
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func InitConfigReplHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||
if !slices.Contains(*x.Config.Conf.DisableWarnings, "--WNonStdTmpDir") && os.TempDir() != "/tmp" {
|
||||
x.Log.Printf("%s: %s", colors.PrintWarn(), "Non-standard value specified for temporary directory")
|
||||
}
|
||||
|
||||
replacements := map[string]any{
|
||||
"%tmp%": filepath.Clean(run_manager.RuntimeDir()),
|
||||
"%path%": *x.Config.Env.NodePath,
|
||||
"%stdout%": "_1STDout",
|
||||
"%stderr%": "_2STDerr",
|
||||
"%1%": "_1STDout",
|
||||
"%2%": "_2STDerr",
|
||||
}
|
||||
|
||||
processConfig(&x.Config.Conf, replacements)
|
||||
|
||||
if !slices.Contains(logs.Levels.Available, *x.Config.Conf.Log.Level) {
|
||||
if !slices.Contains(*x.Config.Conf.DisableWarnings, "--WUndefLogLevel") {
|
||||
x.Log.Printf("%s: %s", colors.PrintWarn(), fmt.Sprintf("Unknown logging level %s, fallback level: %s", *x.Config.Conf.Log.Level, logs.Levels.Fallback))
|
||||
}
|
||||
x.Config.Conf.Log.Level = &logs.Levels.Fallback
|
||||
}
|
||||
}
|
||||
|
||||
func InitConfigPrintHook(ctx context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||
if *x.Config.Conf.Node.ShowConfig {
|
||||
fmt.Printf("Configuration from %s:\n", x.Config.CMDLine.Run.ConfigPath)
|
||||
x.Config.Print(x.Config.Conf)
|
||||
|
||||
fmt.Printf("Environment:\n")
|
||||
x.Config.Print(x.Config.Env)
|
||||
|
||||
if cs.UUID32 != "" && !askConfirm("Is that ok?", true) {
|
||||
x.Log.Printf("Cancel launch")
|
||||
NodeApp.CallFallback(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
x.Log.Printf("Starting \"%s\" node", *x.Config.Conf.Node.Name)
|
||||
}
|
||||
|
||||
func InitSLogHook(_ context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||
cs.Stage = corestate.StageReady
|
||||
x.Log.SetPrefix(colors.SetGreen(fmt.Sprintf("(%s) ", cs.Stage)))
|
||||
|
||||
x.SLog = new(slog.Logger)
|
||||
newSlog, err := logs.SetupLogger(x.Config.Conf.Log)
|
||||
if err != nil {
|
||||
_ = run_manager.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
*x.SLog = *newSlog
|
||||
}
|
||||
|
||||
func processConfig(conf any, replacements map[string]any) error {
|
||||
val := reflect.ValueOf(conf)
|
||||
if val.Kind() == reflect.Ptr {
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
switch val.Kind() {
|
||||
case reflect.Struct:
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
field := val.Field(i)
|
||||
if field.CanAddr() && field.CanSet() {
|
||||
if err := processConfig(field.Addr().Interface(), replacements); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Slice:
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
elem := val.Index(i)
|
||||
if elem.CanAddr() && elem.CanSet() {
|
||||
if err := processConfig(elem.Addr().Interface(), replacements); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
for _, key := range val.MapKeys() {
|
||||
elem := val.MapIndex(key)
|
||||
if elem.CanInterface() {
|
||||
newVal := reflect.New(elem.Type()).Elem()
|
||||
newVal.Set(elem)
|
||||
|
||||
if err := processConfig(newVal.Addr().Interface(), replacements); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val.SetMapIndex(key, newVal)
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.String:
|
||||
str := val.String()
|
||||
|
||||
if replacement, exists := replacements[str]; exists {
|
||||
if err := setValue(val, replacement); err != nil {
|
||||
return fmt.Errorf("failed to set %q: %v", str, err)
|
||||
}
|
||||
} else {
|
||||
for placeholder, replacement := range replacements {
|
||||
if strings.Contains(str, placeholder) {
|
||||
replacementStr, err := toString(replacement)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid replacement for %q: %v", placeholder, err)
|
||||
}
|
||||
newStr := strings.ReplaceAll(str, placeholder, replacementStr)
|
||||
val.SetString(newStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
if !val.IsNil() {
|
||||
elem := val.Elem()
|
||||
if elem.Kind() == reflect.String {
|
||||
str := elem.String()
|
||||
if replacement, exists := replacements[str]; exists {
|
||||
strVal, err := toString(replacement)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot convert replacement to string: %v", err)
|
||||
}
|
||||
elem.SetString(strVal)
|
||||
} else {
|
||||
for placeholder, replacement := range replacements {
|
||||
if strings.Contains(str, placeholder) {
|
||||
replacementStr, err := toString(replacement)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid replacement for %q: %v", placeholder, err)
|
||||
}
|
||||
newStr := strings.ReplaceAll(str, placeholder, replacementStr)
|
||||
elem.SetString(newStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return processConfig(elem.Addr().Interface(), replacements)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setValue(val reflect.Value, replacement any) error {
|
||||
if !val.CanSet() {
|
||||
return fmt.Errorf("value is not settable")
|
||||
}
|
||||
|
||||
replacementVal := reflect.ValueOf(replacement)
|
||||
if replacementVal.Type().AssignableTo(val.Type()) {
|
||||
val.Set(replacementVal)
|
||||
return nil
|
||||
}
|
||||
|
||||
if val.Kind() == reflect.String {
|
||||
str, err := toString(replacement)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot convert replacement to string: %v", err)
|
||||
}
|
||||
val.SetString(str)
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("type mismatch: cannot assign %T to %v", replacement, val.Type())
|
||||
}
|
||||
|
||||
func toString(v any) (string, error) {
|
||||
switch s := v.(type) {
|
||||
case string:
|
||||
return s, nil
|
||||
case fmt.Stringer:
|
||||
return s.String(), nil
|
||||
default:
|
||||
return fmt.Sprint(v), nil
|
||||
}
|
||||
}
|
||||
|
||||
func askConfirm(prompt string, defaultYes bool) bool {
|
||||
ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
||||
|
||||
fmt.Print(prompt)
|
||||
if defaultYes {
|
||||
fmt.Printf(" (%s/%s): ", colors.SetBrightGreen("Y"), colors.SetBrightRed("n"))
|
||||
} else {
|
||||
fmt.Printf(" (%s/%s): ", colors.SetBrightGreen("n"), colors.SetBrightRed("Y"))
|
||||
}
|
||||
|
||||
inputChan := make(chan string, 1)
|
||||
|
||||
go func() {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
text, _ := reader.ReadString('\n')
|
||||
inputChan <- text
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
fmt.Println("")
|
||||
NodeApp.CallFallback(ctx)
|
||||
os.Exit(3)
|
||||
case text := <-inputChan:
|
||||
text = strings.TrimSpace(strings.ToLower(text))
|
||||
if text == "" {
|
||||
return defaultYes
|
||||
}
|
||||
if text == "y" || text == "yes" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
return defaultYes
|
||||
}
|
||||
176
hooks/run.go
Normal file
176
hooks/run.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package hooks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/colors"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/run_manager"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/update"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/logs"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/gateway"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/session"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/sv1"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/netutil"
|
||||
)
|
||||
|
||||
var NodeApp = app.New()
|
||||
|
||||
func Run(cmd *cobra.Command, args []string) {
|
||||
NodeApp.InitialHooks(
|
||||
InitGlobalLoggerHook, InitCorestateHook, InitConfigLoadHook,
|
||||
InitUUUDHook, InitRuntimeHook, InitRunlockHook,
|
||||
InitConfigReplHook, InitConfigPrintHook, InitSLogHook,
|
||||
)
|
||||
|
||||
NodeApp.Run(RunHook)
|
||||
}
|
||||
|
||||
func RunHook(ctx context.Context, cs *corestate.CoreState, x *app.AppX) error {
|
||||
ctxMain, cancelMain := context.WithCancel(ctx)
|
||||
runLockFile := run_manager.File("run.lock")
|
||||
_, err := runLockFile.Open()
|
||||
if err != nil {
|
||||
x.Log.Fatalf("cannot open run.lock: %s", err)
|
||||
}
|
||||
|
||||
_, err = runLockFile.Watch(ctxMain, func() {
|
||||
x.Log.Printf("run.lock was touched")
|
||||
_ = run_manager.Clean()
|
||||
cancelMain()
|
||||
})
|
||||
if err != nil {
|
||||
x.Log.Printf("watch error: %s", err)
|
||||
}
|
||||
|
||||
serverv1 := sv1.InitV1Server(&sv1.HandlerV1InitStruct{
|
||||
X: x,
|
||||
CS: cs,
|
||||
AllowedCmd: regexp.MustCompile(`^[a-zA-Z0-9]+(>[a-zA-Z0-9]+)*$`),
|
||||
Ver: "v1",
|
||||
})
|
||||
|
||||
session_manager := session.New(*x.Config.Conf.HTTPServer.SessionTTL)
|
||||
|
||||
s := gateway.InitGateway(&gateway.GatewayServerInit{
|
||||
SM: session_manager,
|
||||
CS: cs,
|
||||
X: x,
|
||||
}, serverv1)
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.Use(cors.Handler(cors.Options{
|
||||
AllowedOrigins: []string{"*"},
|
||||
AllowedMethods: []string{"POST"},
|
||||
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token", "X-Session-UUID"},
|
||||
AllowCredentials: true,
|
||||
MaxAge: 300,
|
||||
}))
|
||||
r.HandleFunc(config.ComDirRoute, s.Handle)
|
||||
r.Route("/favicon.ico", func(r chi.Router) {
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
})
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: *x.Config.Conf.HTTPServer.Address,
|
||||
Handler: r,
|
||||
ErrorLog: log.New(&logs.SlogWriter{
|
||||
Logger: x.SLog,
|
||||
Level: slog.LevelError,
|
||||
}, "", 0),
|
||||
}
|
||||
|
||||
NodeApp.Fallback(func(ctx context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||
if err := srv.Shutdown(ctxMain); err != nil {
|
||||
x.Log.Printf("%s: Failed to stop the server gracefully: %s", colors.PrintError(), err.Error())
|
||||
} else {
|
||||
x.Log.Printf("Server stopped gracefully")
|
||||
}
|
||||
|
||||
x.Log.Println("Cleaning up...")
|
||||
|
||||
if err := run_manager.Clean(); err != nil {
|
||||
x.Log.Printf("%s: Cleanup error: %s", colors.PrintError(), err.Error())
|
||||
}
|
||||
x.Log.Println("bye!")
|
||||
})
|
||||
|
||||
go func() {
|
||||
defer utils.CatchPanicWithCancel(cancelMain)
|
||||
if *x.Config.Conf.TLS.TlsEnabled {
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", *x.Config.Conf.HTTPServer.Address, *x.Config.Conf.HTTPServer.Port))
|
||||
if err != nil {
|
||||
x.Log.Printf("%s: Failed to start TLS listener: %s", colors.PrintError(), err.Error())
|
||||
cancelMain()
|
||||
return
|
||||
}
|
||||
x.Log.Printf("Serving on %s port %s with TLS... (https://%s%s)", *x.Config.Conf.HTTPServer.Address, *x.Config.Conf.HTTPServer.Port, fmt.Sprintf("%s:%s", *x.Config.Conf.HTTPServer.Address, *x.Config.Conf.HTTPServer.Port), config.ComDirRoute)
|
||||
limitedListener := netutil.LimitListener(listener, 100)
|
||||
if err := srv.ServeTLS(limitedListener, *x.Config.Conf.TLS.CertFile, *x.Config.Conf.TLS.KeyFile); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
x.Log.Printf("%s: Failed to start HTTPS server: %s", colors.PrintError(), err.Error())
|
||||
cancelMain()
|
||||
}
|
||||
} else {
|
||||
x.Log.Printf("Serving on %s port %s... (http://%s%s)", *x.Config.Conf.HTTPServer.Address, *x.Config.Conf.HTTPServer.Port, fmt.Sprintf("%s:%s", *x.Config.Conf.HTTPServer.Address, *x.Config.Conf.HTTPServer.Port), config.ComDirRoute)
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", *x.Config.Conf.HTTPServer.Address, *x.Config.Conf.HTTPServer.Port))
|
||||
if err != nil {
|
||||
x.Log.Printf("%s: Failed to start listener: %s", colors.PrintError(), err.Error())
|
||||
cancelMain()
|
||||
return
|
||||
}
|
||||
limitedListener := netutil.LimitListener(listener, 100)
|
||||
if err := srv.Serve(limitedListener); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
x.Log.Printf("%s: Failed to start HTTP server: %s", colors.PrintError(), err.Error())
|
||||
cancelMain()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
session_manager.StartCleanup(5 * time.Second)
|
||||
|
||||
if *x.Config.Conf.Updates.UpdatesEnabled {
|
||||
go func() {
|
||||
defer utils.CatchPanicWithCancel(cancelMain)
|
||||
updated := update.NewUpdater(&update.UpdaterInit{
|
||||
X: x,
|
||||
Ctx: ctxMain,
|
||||
Cancel: cancelMain,
|
||||
})
|
||||
updated.Shutdownfunc(cancelMain)
|
||||
for {
|
||||
isNewUpdate, err := updated.CkeckUpdates()
|
||||
if err != nil {
|
||||
x.Log.Printf("Failed to check for updates: %s", err.Error())
|
||||
}
|
||||
if isNewUpdate {
|
||||
if err := updated.Update(); err != nil {
|
||||
x.Log.Printf("Failed to update: %s", err.Error())
|
||||
} else {
|
||||
x.Log.Printf("Update completed successfully")
|
||||
}
|
||||
}
|
||||
time.Sleep(*x.Config.Conf.Updates.CheckInterval)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
<-ctxMain.Done()
|
||||
NodeApp.CallFallback(ctx)
|
||||
return nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package logs
|
||||
package colors
|
||||
|
||||
import "fmt"
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -16,6 +15,7 @@ import (
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/run_manager"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
@@ -38,20 +38,23 @@ type UpdaterContract interface {
|
||||
}
|
||||
|
||||
type Updater struct {
|
||||
log *log.Logger
|
||||
config *config.Conf
|
||||
env *config.Env
|
||||
x *app.AppX
|
||||
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func NewUpdater(ctx context.Context, log *log.Logger, cfg *config.Conf, env *config.Env) *Updater {
|
||||
type UpdaterInit struct {
|
||||
X *app.AppX
|
||||
Ctx context.Context
|
||||
Cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func NewUpdater(o *UpdaterInit) *Updater {
|
||||
return &Updater{
|
||||
log: log,
|
||||
config: cfg,
|
||||
env: env,
|
||||
ctx: ctx,
|
||||
x: o.X,
|
||||
ctx: o.Ctx,
|
||||
cancel: o.Cancel,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +122,7 @@ func isVersionNewer(current, latest Version) bool {
|
||||
func (u *Updater) GetCurrentVersion() (Version, Branch, error) {
|
||||
version, branch, err := splitVersionString(string(config.NodeVersion))
|
||||
if err != nil {
|
||||
u.log.Printf("Failed to parse version string: %s", err.Error())
|
||||
u.x.Log.Printf("Failed to parse version string: %s", err.Error())
|
||||
return "", "", err
|
||||
}
|
||||
switch branch {
|
||||
@@ -131,28 +134,28 @@ func (u *Updater) GetCurrentVersion() (Version, Branch, error) {
|
||||
}
|
||||
|
||||
func (u *Updater) GetLatestVersion(updateBranch Branch) (Version, Branch, error) {
|
||||
repoURL := u.config.Updates.RepositoryURL
|
||||
repoURL := *u.x.Config.Conf.Updates.RepositoryURL
|
||||
if repoURL == "" {
|
||||
u.log.Printf("Failed to get latest version: %s", "RepositoryURL is empty in config")
|
||||
u.x.Log.Printf("Failed to get latest version: %s", "RepositoryURL is empty in config")
|
||||
return "", "", errors.New("repository URL is empty")
|
||||
}
|
||||
if !strings.HasPrefix(repoURL, "http://") && !strings.HasPrefix(repoURL, "https://") {
|
||||
u.log.Printf("Failed to get latest version: %s: %s", "RepositoryURL does not start with http:// or https:/", repoURL)
|
||||
u.x.Log.Printf("Failed to get latest version: %s: %s", "RepositoryURL does not start with http:// or https:/", repoURL)
|
||||
return "", "", errors.New("repository URL must start with http:// or https://")
|
||||
}
|
||||
response, err := http.Get(repoURL + "/" + config.ActualFileName)
|
||||
if err != nil {
|
||||
u.log.Printf("Failed to fetch latest version: %s", err.Error())
|
||||
u.x.Log.Printf("Failed to fetch latest version: %s", err.Error())
|
||||
return "", "", err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != http.StatusOK {
|
||||
u.log.Printf("Failed to fetch latest version: HTTP status %d", response.StatusCode)
|
||||
u.x.Log.Printf("Failed to fetch latest version: HTTP status %d", response.StatusCode)
|
||||
return "", "", errors.New("failed to fetch latest version, status code: " + http.StatusText(response.StatusCode))
|
||||
}
|
||||
data, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
u.log.Printf("Failed to read latest version response: %s", err.Error())
|
||||
u.x.Log.Printf("Failed to read latest version response: %s", err.Error())
|
||||
return "", "", err
|
||||
}
|
||||
lines := strings.Split(string(data), "\n")
|
||||
@@ -163,7 +166,7 @@ func (u *Updater) GetLatestVersion(updateBranch Branch) (Version, Branch, error)
|
||||
}
|
||||
version, branch, err := splitVersionString(string(line))
|
||||
if err != nil {
|
||||
u.log.Printf("Failed to parse version string: %s", err.Error())
|
||||
u.x.Log.Printf("Failed to parse version string: %s", err.Error())
|
||||
return "", "", err
|
||||
}
|
||||
if branch == updateBranch {
|
||||
@@ -189,7 +192,7 @@ func (u *Updater) CkeckUpdates() (IsNewUpdate, error) {
|
||||
}
|
||||
|
||||
func (u *Updater) Update() error {
|
||||
if !u.config.Updates.UpdatesEnabled {
|
||||
if !*u.x.Config.Conf.Updates.UpdatesEnabled {
|
||||
return errors.New("updates are disabled in config, skipping update")
|
||||
}
|
||||
|
||||
@@ -209,7 +212,7 @@ func (u *Updater) Update() error {
|
||||
}
|
||||
|
||||
updateArchiveName := fmt.Sprintf("%s.v%s-%s", config.UpdateArchiveName, latestVersion, latestBranch)
|
||||
updateDest := fmt.Sprintf("%s/%s.%s", u.config.Updates.RepositoryURL, updateArchiveName, "tar.gz")
|
||||
updateDest := fmt.Sprintf("%s/%s.%s", *u.x.Config.Conf.Updates.RepositoryURL, updateArchiveName, "tar.gz")
|
||||
|
||||
resp, err := http.Get(updateDest)
|
||||
if err != nil {
|
||||
@@ -275,7 +278,7 @@ func (u *Updater) Update() error {
|
||||
|
||||
func (u *Updater) InstallAndRestart() error {
|
||||
|
||||
nodePath := u.env.NodePath
|
||||
nodePath := *u.x.Config.Env.NodePath
|
||||
if nodePath == "" {
|
||||
return errors.New("GS_NODE_PATH environment variable is not set")
|
||||
}
|
||||
@@ -303,12 +306,7 @@ func (u *Updater) InstallAndRestart() error {
|
||||
return fmt.Errorf("failed to chmod: %w", err)
|
||||
}
|
||||
|
||||
u.log.Printf("Launching new version: path is %s", targetPath)
|
||||
// cmd := exec.Command(targetPath, os.Args[1:]...)
|
||||
// cmd.Env = os.Environ()
|
||||
// cmd.Stdout = os.Stdout
|
||||
// cmd.Stderr = os.Stderr
|
||||
// cmd.Stdin = os.Stdin
|
||||
u.x.Log.Printf("Launching new version: path is %s", targetPath)
|
||||
args := os.Args
|
||||
args[0] = targetPath
|
||||
env := utils.SetEviron(os.Environ(), "GS_PARENT_PID=-1")
|
||||
@@ -317,17 +315,6 @@ func (u *Updater) InstallAndRestart() error {
|
||||
return err
|
||||
}
|
||||
return syscall.Exec(targetPath, args, env)
|
||||
//u.cancel()
|
||||
|
||||
// TODO: fix this crap and find a better way to update without errors
|
||||
// for {
|
||||
// _, err := run_manager.Get("run.lock")
|
||||
// if err != nil {
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
|
||||
// return cmd.Start()
|
||||
}
|
||||
|
||||
func (u *Updater) Shutdownfunc(f context.CancelFunc) {
|
||||
|
||||
34
internal/core/utils/panic.go
Normal file
34
internal/core/utils/panic.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"log"
|
||||
"runtime"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func CatchPanic() {
|
||||
if err := recover(); err != nil {
|
||||
stack := make([]byte, 8096)
|
||||
stack = stack[:runtime.Stack(stack, false)]
|
||||
log.Printf("recovered panic:\n%s", stack)
|
||||
}
|
||||
}
|
||||
|
||||
func CatchPanicWithCancel(cancel context.CancelFunc) {
|
||||
if err := recover(); err != nil {
|
||||
stack := make([]byte, 8096)
|
||||
stack = stack[:runtime.Stack(stack, false)]
|
||||
log.Printf("recovered panic:\n%s", stack)
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
|
||||
func CatchPanicWithFallback(onPanic func(any)) {
|
||||
if err := recover(); err != nil {
|
||||
stack := make([]byte, 8096)
|
||||
stack = stack[:runtime.Stack(stack, false)]
|
||||
log.Printf("recovered panic:\n%s", stack)
|
||||
onPanic(err)
|
||||
}
|
||||
}
|
||||
9
internal/core/utils/safe_fetch.go
Normal file
9
internal/core/utils/safe_fetch.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package utils
|
||||
|
||||
// SafeFetch safely fetches data. If v = nil, a fallback value is returned.
|
||||
func SafeFetch[T any](v *T, fallback T) T {
|
||||
if v == nil {
|
||||
return fallback
|
||||
}
|
||||
return *v
|
||||
}
|
||||
@@ -6,31 +6,36 @@ import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/update"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
||||
)
|
||||
|
||||
type AppContract interface {
|
||||
InitialHooks(fn ...func(cs *corestate.CoreState, x *AppX))
|
||||
InitialHooks(fn ...func(ctx context.Context, cs *corestate.CoreState, x *AppX))
|
||||
Run(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX) error)
|
||||
Fallback(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX))
|
||||
|
||||
CallFallback(ctx context.Context)
|
||||
}
|
||||
|
||||
type App struct {
|
||||
initHooks []func(cs *corestate.CoreState, x *AppX)
|
||||
initHooks []func(ctx context.Context, cs *corestate.CoreState, x *AppX)
|
||||
runHook func(ctx context.Context, cs *corestate.CoreState, x *AppX) error
|
||||
fallback func(ctx context.Context, cs *corestate.CoreState, x *AppX)
|
||||
|
||||
Corestate *corestate.CoreState
|
||||
AppX *AppX
|
||||
|
||||
fallbackOnce sync.Once
|
||||
}
|
||||
|
||||
type AppX struct {
|
||||
Config *config.Compositor
|
||||
Log *log.Logger
|
||||
SLog *slog.Logger
|
||||
Updated *update.Updater
|
||||
Config *config.Compositor
|
||||
Log *log.Logger
|
||||
SLog *slog.Logger
|
||||
}
|
||||
|
||||
func New() AppContract {
|
||||
@@ -42,23 +47,49 @@ func New() AppContract {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) InitialHooks(fn ...func(cs *corestate.CoreState, x *AppX)) {
|
||||
func (a *App) InitialHooks(fn ...func(ctx context.Context, cs *corestate.CoreState, x *AppX)) {
|
||||
a.initHooks = append(a.initHooks, fn...)
|
||||
}
|
||||
|
||||
func (a *App) Fallback(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX)) {
|
||||
a.fallback = fn
|
||||
}
|
||||
|
||||
func (a *App) Run(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX) error) {
|
||||
a.runHook = fn
|
||||
|
||||
for _, hook := range a.initHooks {
|
||||
hook(a.Corestate, a.AppX)
|
||||
}
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
||||
defer stop()
|
||||
|
||||
if a.runHook != nil {
|
||||
if err := a.runHook(ctx, a.Corestate, a.AppX); err != nil {
|
||||
log.Fatalf("fatal in Run: %v", err)
|
||||
for _, hook := range a.initHooks {
|
||||
hook(ctx, a.Corestate, a.AppX)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
a.AppX.Log.Printf("PANIC recovered: %v", r)
|
||||
if a.fallback != nil {
|
||||
a.fallback(ctx, a.Corestate, a.AppX)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
var runErr error
|
||||
if a.runHook != nil {
|
||||
runErr = a.runHook(ctx, a.Corestate, a.AppX)
|
||||
}
|
||||
|
||||
if runErr != nil {
|
||||
a.AppX.Log.Fatalf("fatal in Run: %v", runErr)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) CallFallback(ctx context.Context) {
|
||||
a.fallbackOnce.Do(func() {
|
||||
if a.fallback != nil {
|
||||
a.fallback(ctx, a.Corestate, a.AppX)
|
||||
}
|
||||
os.Exit(0)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -43,10 +43,13 @@ func (c *Compositor) LoadConf(path string) error {
|
||||
v.SetConfigType("yaml")
|
||||
|
||||
// defaults
|
||||
v.SetDefault("mode", "dev")
|
||||
v.SetDefault("com_dir", "./com/")
|
||||
v.SetDefault("node.name", "noname")
|
||||
v.SetDefault("node.mode", "dev")
|
||||
v.SetDefault("node.show_config", "false")
|
||||
v.SetDefault("node.com_dir", "./com/")
|
||||
v.SetDefault("http_server.address", "0.0.0.0")
|
||||
v.SetDefault("http_server.port", "8080")
|
||||
v.SetDefault("http_server.session_ttl", "30m")
|
||||
v.SetDefault("http_server.timeout", "5s")
|
||||
v.SetDefault("http_server.idle_timeout", "60s")
|
||||
v.SetDefault("tls.enabled", false)
|
||||
@@ -55,8 +58,9 @@ func (c *Compositor) LoadConf(path string) error {
|
||||
v.SetDefault("updates.enabled", false)
|
||||
v.SetDefault("updates.check_interval", "2h")
|
||||
v.SetDefault("updates.wanted_version", "latest-stable")
|
||||
v.SetDefault("log.json_format", "false")
|
||||
v.SetDefault("log.level", "info")
|
||||
v.SetDefault("log.out_path", "")
|
||||
v.SetDefault("log.output", "%2%")
|
||||
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
return fmt.Errorf("error reading config: %w", err)
|
||||
|
||||
@@ -18,51 +18,53 @@ type Compositor struct {
|
||||
}
|
||||
|
||||
type Conf struct {
|
||||
Mode string `mapstructure:"mode"`
|
||||
ComDir string `mapstructure:"com_dir"`
|
||||
HTTPServer HTTPServer `mapstructure:"http_server"`
|
||||
TLS TLS `mapstructure:"tls"`
|
||||
Updates Updates `mapstructure:"updates"`
|
||||
Log Log `mapstructure:"log"`
|
||||
DisableWarnings []string `mapstructure:"disable_warnings"`
|
||||
Node *Node `mapstructure:"node"`
|
||||
HTTPServer *HTTPServer `mapstructure:"http_server"`
|
||||
TLS *TLS `mapstructure:"tls"`
|
||||
Updates *Updates `mapstructure:"updates"`
|
||||
Log *Log `mapstructure:"log"`
|
||||
DisableWarnings *[]string `mapstructure:"disable_warnings"`
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
Mode *string `mapstructure:"mode"`
|
||||
Name *string `mapstructure:"name"`
|
||||
ShowConfig *bool `mapstructure:"show_config"`
|
||||
ComDir *string `mapstructure:"com_dir"`
|
||||
}
|
||||
|
||||
type HTTPServer struct {
|
||||
Address string `mapstructure:"address"`
|
||||
Port string `mapstructure:"port"`
|
||||
Timeout time.Duration `mapstructure:"timeout"`
|
||||
IdleTimeout time.Duration `mapstructure:"idle_timeout"`
|
||||
HTTPServer_Api HTTPServer_Api `mapstructure:"api"`
|
||||
}
|
||||
|
||||
type HTTPServer_Api struct {
|
||||
LatestVer string `mapstructure:"latest-version"`
|
||||
Layers []string `mapstructure:"layers"`
|
||||
Address *string `mapstructure:"address"`
|
||||
Port *string `mapstructure:"port"`
|
||||
SessionTTL *time.Duration `mapstructure:"session_ttl"`
|
||||
Timeout *time.Duration `mapstructure:"timeout"`
|
||||
IdleTimeout *time.Duration `mapstructure:"idle_timeout"`
|
||||
}
|
||||
|
||||
type TLS struct {
|
||||
TlsEnabled bool `mapstructure:"enabled"`
|
||||
CertFile string `mapstructure:"cert_file"`
|
||||
KeyFile string `mapstructure:"key_file"`
|
||||
TlsEnabled *bool `mapstructure:"enabled"`
|
||||
CertFile *string `mapstructure:"cert_file"`
|
||||
KeyFile *string `mapstructure:"key_file"`
|
||||
}
|
||||
|
||||
type Updates struct {
|
||||
UpdatesEnabled bool `mapstructure:"enabled"`
|
||||
CheckInterval time.Duration `mapstructure:"check_interval"`
|
||||
RepositoryURL string `mapstructure:"repository_url"`
|
||||
WantedVersion string `mapstructure:"wanted_version"`
|
||||
UpdatesEnabled *bool `mapstructure:"enabled"`
|
||||
CheckInterval *time.Duration `mapstructure:"check_interval"`
|
||||
RepositoryURL *string `mapstructure:"repository_url"`
|
||||
WantedVersion *string `mapstructure:"wanted_version"`
|
||||
}
|
||||
|
||||
type Log struct {
|
||||
Level string `mapstructure:"level"`
|
||||
OutPath string `mapstructure:"out_path"`
|
||||
JSON *bool `mapstructure:"json_format"`
|
||||
Level *string `mapstructure:"level"`
|
||||
OutPath *string `mapstructure:"output"`
|
||||
}
|
||||
|
||||
// ConfigEnv structure for environment variables
|
||||
type Env struct {
|
||||
ConfigPath string `mapstructure:"config_path"`
|
||||
NodePath string `mapstructure:"node_path"`
|
||||
ParentStagePID int `mapstructure:"parent_pid"`
|
||||
ConfigPath *string `mapstructure:"config_path"`
|
||||
NodePath *string `mapstructure:"node_path"`
|
||||
ParentStagePID *int `mapstructure:"parent_pid"`
|
||||
}
|
||||
|
||||
type CMDLine struct {
|
||||
|
||||
@@ -2,6 +2,8 @@ package config
|
||||
|
||||
import "os"
|
||||
|
||||
// TODO: Need to make a more harmonious and understandable way of storing global variables
|
||||
|
||||
// UUIDLength is uuids length for sessions. By default it is 16 bytes.
|
||||
var UUIDLength int = 16
|
||||
|
||||
|
||||
72
internal/engine/config/print.go
Normal file
72
internal/engine/config/print.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/colors"
|
||||
)
|
||||
|
||||
func (c *Compositor) Print(v any) {
|
||||
c.printConfig(v, " ")
|
||||
}
|
||||
|
||||
func (c *Compositor) printConfig(v any, prefix string) {
|
||||
val := reflect.ValueOf(v)
|
||||
if val.Kind() == reflect.Ptr {
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
typ := val.Type()
|
||||
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
field := val.Field(i)
|
||||
fieldType := typ.Field(i)
|
||||
|
||||
fieldName := fieldType.Name
|
||||
if tag, ok := fieldType.Tag.Lookup("mapstructure"); ok {
|
||||
if tag != "" {
|
||||
fieldName = tag
|
||||
}
|
||||
}
|
||||
|
||||
coloredFieldName := colors.SetBrightCyan(fieldName)
|
||||
|
||||
if field.Kind() == reflect.Ptr {
|
||||
if field.IsNil() {
|
||||
fmt.Printf("%s%s: %s\n", prefix, coloredFieldName, colors.SetBrightRed("<nil>"))
|
||||
continue
|
||||
}
|
||||
field = field.Elem()
|
||||
}
|
||||
|
||||
if field.Kind() == reflect.Struct {
|
||||
if field.Type() == reflect.TypeOf(time.Duration(0)) {
|
||||
duration := field.Interface().(time.Duration)
|
||||
fmt.Printf("%s%s: %s\n",
|
||||
prefix,
|
||||
coloredFieldName,
|
||||
colors.SetBrightYellow(duration.String()))
|
||||
} else {
|
||||
fmt.Printf("%s%s:\n", prefix, coloredFieldName)
|
||||
c.printConfig(field.Addr().Interface(), prefix+" ")
|
||||
}
|
||||
} else if field.Kind() == reflect.Slice {
|
||||
fmt.Printf("%s%s: %s\n",
|
||||
prefix,
|
||||
coloredFieldName,
|
||||
colors.SetBrightYellow(fmt.Sprintf("%v", field.Interface())))
|
||||
} else {
|
||||
value := field.Interface()
|
||||
valueStr := fmt.Sprintf("%v", value)
|
||||
if field.Kind() == reflect.String {
|
||||
valueStr = fmt.Sprintf("\"%s\"", value)
|
||||
}
|
||||
fmt.Printf("%s%s: %s\n",
|
||||
prefix,
|
||||
coloredFieldName,
|
||||
colors.SetBrightYellow(valueStr))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,16 +10,25 @@ import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/run_manager"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
var GlobalLevel slog.Level
|
||||
|
||||
type levelsStruct struct {
|
||||
Available []string
|
||||
Fallback string
|
||||
}
|
||||
|
||||
var Levels = levelsStruct{
|
||||
Available: []string{
|
||||
"debug", "info",
|
||||
},
|
||||
Fallback: "info",
|
||||
}
|
||||
|
||||
type SlogWriter struct {
|
||||
Logger *slog.Logger
|
||||
Level slog.Level
|
||||
@@ -32,11 +41,11 @@ func (w *SlogWriter) Write(p []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
// SetupLogger initializes and returns a logger based on the provided environment.
|
||||
func SetupLogger(o config.Log) (*slog.Logger, error) {
|
||||
func SetupLogger(o *config.Log) (*slog.Logger, error) {
|
||||
var handlerOpts = slog.HandlerOptions{}
|
||||
var writer io.Writer = os.Stdout
|
||||
|
||||
switch o.Level {
|
||||
switch *o.Level {
|
||||
case "debug":
|
||||
GlobalLevel = slog.LevelDebug
|
||||
handlerOpts.Level = slog.LevelDebug
|
||||
@@ -48,32 +57,14 @@ func SetupLogger(o config.Log) (*slog.Logger, error) {
|
||||
handlerOpts.Level = slog.LevelInfo
|
||||
}
|
||||
|
||||
if o.OutPath != "" {
|
||||
repl := map[string]string{
|
||||
"tmp": filepath.Clean(run_manager.RuntimeDir()),
|
||||
}
|
||||
re := regexp.MustCompile(`%(\w+)%`)
|
||||
result := re.ReplaceAllStringFunc(o.OutPath, func(match string) string {
|
||||
sub := re.FindStringSubmatch(match)
|
||||
if len(sub) < 2 {
|
||||
return match
|
||||
}
|
||||
key := sub[1]
|
||||
if val, ok := repl[key]; ok {
|
||||
return val
|
||||
}
|
||||
return match
|
||||
})
|
||||
|
||||
if strings.Contains(o.OutPath, "%tmp%") {
|
||||
relPath := strings.TrimPrefix(result, filepath.Clean(run_manager.RuntimeDir()))
|
||||
if err := run_manager.SetDir(relPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
switch *o.OutPath {
|
||||
case "_1STDout":
|
||||
writer = os.Stdout
|
||||
case "_2STDerr":
|
||||
writer = os.Stderr
|
||||
default:
|
||||
logFile := &lumberjack.Logger{
|
||||
Filename: filepath.Join(result, "event.log"),
|
||||
Filename: filepath.Join(*o.OutPath, "event.log"),
|
||||
MaxSize: 10,
|
||||
MaxBackups: 5,
|
||||
MaxAge: 28,
|
||||
@@ -82,6 +73,13 @@ func SetupLogger(o config.Log) (*slog.Logger, error) {
|
||||
writer = logFile
|
||||
}
|
||||
|
||||
log := slog.New(slog.NewJSONHandler(writer, &handlerOpts))
|
||||
var handler slog.Handler
|
||||
|
||||
if *o.JSON {
|
||||
handler = slog.NewJSONHandler(writer, &handlerOpts)
|
||||
} else {
|
||||
handler = slog.NewTextHandler(writer, &handlerOpts)
|
||||
}
|
||||
log := slog.New(handler)
|
||||
return log, nil
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package gateway
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/session"
|
||||
)
|
||||
|
||||
// serversApiVer is a type alias for string, used to represent API version strings in the GeneralServer.
|
||||
@@ -13,7 +15,7 @@ type serversApiVer string
|
||||
|
||||
type ServerApiContract interface {
|
||||
GetVersion() string
|
||||
Handle(r *http.Request, req *rpc.RPCRequest) *rpc.RPCResponse
|
||||
Handle(ctx context.Context, sid string, r *http.Request, req *rpc.RPCRequest) *rpc.RPCResponse
|
||||
}
|
||||
|
||||
// GeneralServer implements the GeneralServerApiContract and serves as a router for different API versions.
|
||||
@@ -22,6 +24,7 @@ type GatewayServer struct {
|
||||
// The key is the version string, and the value is the server implementing GeneralServerApi
|
||||
servers map[serversApiVer]ServerApiContract
|
||||
|
||||
log *slog.Logger
|
||||
cfg *config.Conf
|
||||
sm *session.SessionManager
|
||||
cs *corestate.CoreState
|
||||
x *app.AppX
|
||||
}
|
||||
|
||||
@@ -2,23 +2,26 @@ package gateway
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/session"
|
||||
)
|
||||
|
||||
// GeneralServerInit structure only for initialization general server.
|
||||
type GatewayServerInit struct {
|
||||
Log *slog.Logger
|
||||
Config *config.Conf
|
||||
SM *session.SessionManager
|
||||
CS *corestate.CoreState
|
||||
X *app.AppX
|
||||
}
|
||||
|
||||
// InitGeneral initializes a new GeneralServer with the provided configuration and registered servers.
|
||||
func InitGateway(o *GatewayServerInit, servers ...ServerApiContract) *GatewayServer {
|
||||
general := &GatewayServer{
|
||||
servers: make(map[serversApiVer]ServerApiContract),
|
||||
cfg: o.Config,
|
||||
log: o.Log,
|
||||
sm: o.SM,
|
||||
cs: o.CS,
|
||||
x: o.X,
|
||||
}
|
||||
|
||||
// register the provided servers
|
||||
|
||||
@@ -1,19 +1,44 @@
|
||||
package gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func (gs *GatewayServer) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context() // TODO
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
sessionUUID := r.Header.Get("X-Session-UUID")
|
||||
if sessionUUID == "" {
|
||||
sessionUUID = uuid.New().String()
|
||||
}
|
||||
gs.x.SLog.Debug("new request", slog.String("session-uuid", sessionUUID), slog.Group("connection", slog.String("ip", r.RemoteAddr)))
|
||||
|
||||
w.Header().Set("X-Session-UUID", sessionUUID)
|
||||
if !gs.sm.Add(sessionUUID) {
|
||||
gs.x.SLog.Debug("session is busy", slog.String("session-uuid", sessionUUID))
|
||||
rpc.WriteError(w, &rpc.RPCResponse{
|
||||
Error: map[string]any{
|
||||
"code": rpc.ErrSessionIsBusy,
|
||||
"message": rpc.ErrSessionIsBusyS,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
defer gs.sm.Delete(sessionUUID)
|
||||
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
gs.x.SLog.Debug("failed to read body", slog.String("err", err.Error()))
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
rpc.WriteError(w, &rpc.RPCResponse{
|
||||
JSONRPC: rpc.JSONRPCVersion,
|
||||
@@ -23,7 +48,7 @@ func (gs *GatewayServer) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
"message": rpc.ErrInternalErrorS,
|
||||
},
|
||||
})
|
||||
gs.log.Info("invalid request received", slog.String("issue", rpc.ErrInternalErrorS))
|
||||
gs.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrInternalErrorS))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -33,6 +58,7 @@ func (gs *GatewayServer) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
var single rpc.RPCRequest
|
||||
if batch == nil {
|
||||
if err := json.Unmarshal(body, &single); err != nil {
|
||||
gs.x.SLog.Debug("failed to parse json", slog.String("err", err.Error()))
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
rpc.WriteError(w, &rpc.RPCResponse{
|
||||
JSONRPC: rpc.JSONRPCVersion,
|
||||
@@ -42,10 +68,14 @@ func (gs *GatewayServer) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
"message": rpc.ErrParseErrorS,
|
||||
},
|
||||
})
|
||||
gs.log.Info("invalid request received", slog.String("issue", rpc.ErrParseErrorS))
|
||||
gs.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrParseErrorS))
|
||||
return
|
||||
}
|
||||
resp := gs.Route(ctx, sessionUUID, r, &single)
|
||||
if resp == nil {
|
||||
w.Write([]byte(""))
|
||||
return
|
||||
}
|
||||
resp := gs.Route(r, &single)
|
||||
rpc.WriteResponse(w, resp)
|
||||
return
|
||||
}
|
||||
@@ -57,7 +87,7 @@ func (gs *GatewayServer) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
wg.Add(1)
|
||||
go func(req rpc.RPCRequest) {
|
||||
defer wg.Done()
|
||||
res := gs.Route(r, &req)
|
||||
res := gs.Route(ctx, sessionUUID, r, &req)
|
||||
if res != nil {
|
||||
responses <- *res
|
||||
}
|
||||
@@ -72,25 +102,31 @@ func (gs *GatewayServer) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
if len(result) > 0 {
|
||||
json.NewEncoder(w).Encode(result)
|
||||
} else {
|
||||
w.Write([]byte("[]"))
|
||||
}
|
||||
}
|
||||
|
||||
func (gs *GatewayServer) Route(r *http.Request, req *rpc.RPCRequest) *rpc.RPCResponse {
|
||||
func (gs *GatewayServer) Route(ctx context.Context, sid string, r *http.Request, req *rpc.RPCRequest) (resp *rpc.RPCResponse) {
|
||||
defer utils.CatchPanicWithFallback(func(rec any) {
|
||||
gs.x.SLog.Error("panic caught in handler", slog.Any("error", rec))
|
||||
resp = rpc.NewError(rpc.ErrInternalError, "Internal server error (panic)", nil, req.ID)
|
||||
})
|
||||
if req.JSONRPC != rpc.JSONRPCVersion {
|
||||
gs.log.Info("invalid request received", slog.String("issue", rpc.ErrInvalidRequestS), slog.String("requested-version", req.JSONRPC))
|
||||
return rpc.NewError(rpc.ErrInvalidRequest, rpc.ErrInvalidRequestS, req.ID)
|
||||
gs.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrInvalidRequestS), slog.String("requested-version", req.JSONRPC))
|
||||
return rpc.NewError(rpc.ErrInvalidRequest, rpc.ErrInvalidRequestS, nil, req.ID)
|
||||
}
|
||||
|
||||
server, ok := gs.servers[serversApiVer(req.ContextVersion)]
|
||||
if !ok {
|
||||
gs.log.Info("invalid request received", slog.String("issue", rpc.ErrContextVersionS), slog.String("requested-version", req.ContextVersion))
|
||||
return rpc.NewError(rpc.ErrContextVersion, rpc.ErrContextVersionS, req.ID)
|
||||
gs.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrContextVersionS), slog.String("requested-version", req.ContextVersion))
|
||||
return rpc.NewError(rpc.ErrContextVersion, rpc.ErrContextVersionS, nil, req.ID)
|
||||
}
|
||||
|
||||
resp := server.Handle(r, req)
|
||||
// checks if request is notification
|
||||
if req.ID == nil {
|
||||
go server.Handle(ctx, sid, r, req)
|
||||
return nil
|
||||
}
|
||||
return resp
|
||||
return server.Handle(ctx, sid, r, req)
|
||||
}
|
||||
|
||||
@@ -18,4 +18,13 @@ const (
|
||||
|
||||
ErrContextVersion = -32010
|
||||
ErrContextVersionS = "Invalid context version"
|
||||
|
||||
ErrInvalidMethodFormat = -32020
|
||||
ErrInvalidMethodFormatS = "Invalid method format"
|
||||
|
||||
ErrMethodIsMissing = -32020
|
||||
ErrMethodIsMissingS = "Method is missing"
|
||||
|
||||
ErrSessionIsBusy = -32030
|
||||
ErrSessionIsBusyS = "The session is busy"
|
||||
)
|
||||
|
||||
@@ -2,7 +2,18 @@ package rpc
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
func NewError(code int, message string, id *json.RawMessage) *RPCResponse {
|
||||
func NewError(code int, message string, data any, id *json.RawMessage) *RPCResponse {
|
||||
if data != nil {
|
||||
return &RPCResponse{
|
||||
JSONRPC: JSONRPCVersion,
|
||||
ID: id,
|
||||
Error: map[string]any{
|
||||
"code": code,
|
||||
"message": message,
|
||||
"data": data,
|
||||
},
|
||||
}
|
||||
}
|
||||
return &RPCResponse{
|
||||
JSONRPC: JSONRPCVersion,
|
||||
ID: id,
|
||||
|
||||
47
internal/server/session/manager.go
Normal file
47
internal/server/session/manager.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SessionManagerContract interface {
|
||||
Add(uuid string) bool
|
||||
Delete(uuid string)
|
||||
StartCleanup(interval time.Duration)
|
||||
}
|
||||
|
||||
type SessionManager struct {
|
||||
sessions sync.Map
|
||||
ttl time.Duration
|
||||
}
|
||||
|
||||
func New(ttl time.Duration) *SessionManager {
|
||||
return &SessionManager{
|
||||
ttl: ttl,
|
||||
}
|
||||
}
|
||||
|
||||
func (sm *SessionManager) Add(uuid string) bool {
|
||||
_, loaded := sm.sessions.LoadOrStore(uuid, time.Now().Add(sm.ttl))
|
||||
return !loaded
|
||||
}
|
||||
|
||||
func (sm *SessionManager) Delete(uuid string) {
|
||||
sm.sessions.Delete(uuid)
|
||||
}
|
||||
|
||||
func (sm *SessionManager) StartCleanup(interval time.Duration) {
|
||||
go func() {
|
||||
ticker := time.NewTicker(interval)
|
||||
for range ticker.C {
|
||||
sm.sessions.Range(func(key, value any) bool {
|
||||
expiry := value.(time.Time)
|
||||
if time.Now().After(expiry) {
|
||||
sm.sessions.Delete(key)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -1,342 +1,29 @@
|
||||
package sv1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
||||
)
|
||||
|
||||
func (h *HandlerV1) Handle(r *http.Request, req *rpc.RPCRequest) *rpc.RPCResponse {
|
||||
return rpc.NewResponse("Hi", req.ID) // test answer to make sure everything works
|
||||
}
|
||||
func (h *HandlerV1) Handle(_ context.Context, sid string, r *http.Request, req *rpc.RPCRequest) *rpc.RPCResponse {
|
||||
if req.Method == "" {
|
||||
h.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrMethodNotFoundS), slog.String("requested-method", req.Method))
|
||||
return rpc.NewError(rpc.ErrMethodIsMissing, rpc.ErrMethodIsMissingS, nil, req.ID)
|
||||
}
|
||||
|
||||
// func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
// var req PettiRequest
|
||||
// // server, ok := s.servers[serversApiVer(payload.PettiVer)]
|
||||
// // if !ok {
|
||||
// // WriteRouterError(w, &RouterError{
|
||||
// // Status: "error",
|
||||
// // StatusCode: http.StatusBadRequest,
|
||||
// // Payload: map[string]any{
|
||||
// // "Message": InvalidProtovolVersion,
|
||||
// // },
|
||||
// // })
|
||||
// // s.log.Info("invalid request received", slog.String("issue", InvalidProtovolVersion), slog.String("requested-version", payload.PettiVer))
|
||||
// // return
|
||||
// // }
|
||||
// if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
// utils.WriteJSONError(w, http.StatusBadRequest, "невалидный JSON: "+err.Error())
|
||||
// return
|
||||
// }
|
||||
|
||||
// if req.PettiVer == "" {
|
||||
// utils.WriteJSONError(w, http.StatusBadRequest, "отсутствует PettiVer")
|
||||
// return
|
||||
// }
|
||||
// if req.PettiVer != h.GetVersion() {
|
||||
// utils.WriteJSONError(w, http.StatusBadRequest, "неподдерживаемая версия PettiVer")
|
||||
// return
|
||||
// }
|
||||
// if req.PackageType.Request == "" {
|
||||
// utils.WriteJSONError(w, http.StatusBadRequest, "отсутствует PackageType.Request")
|
||||
// return
|
||||
// }
|
||||
// if req.Payload == nil {
|
||||
// utils.WriteJSONError(w, http.StatusBadRequest, "отсутствует Payload")
|
||||
// return
|
||||
// }
|
||||
// cmdRaw, ok := req.Payload["Exec"].(string)
|
||||
// if !ok || cmdRaw == "" {
|
||||
// utils.WriteJSONError(w, http.StatusBadRequest, "Payload.Exec отсутствует или некорректен")
|
||||
// return
|
||||
// }
|
||||
// cmd := cmdRaw
|
||||
|
||||
// if !h.allowedCmd.MatchString(string([]rune(cmd)[0])) || !h.listAllowedCmd.MatchString(cmd) {
|
||||
// utils.WriteJSONError(w, http.StatusBadRequest, "команда запрещена")
|
||||
// return
|
||||
// }
|
||||
|
||||
// // ===== Проверка скрипта
|
||||
// scriptPath := h.comMatch(h.GetVersion(), cmd)
|
||||
// if scriptPath == "" {
|
||||
// utils.WriteJSONError(w, http.StatusNotFound, "команда не найдена")
|
||||
// return
|
||||
// }
|
||||
// fullPath := filepath.Join(h.cfg.ComDir, scriptPath)
|
||||
// if _, err := os.Stat(fullPath); err != nil {
|
||||
// utils.WriteJSONError(w, http.StatusNotFound, "файл команды не найден")
|
||||
// return
|
||||
// }
|
||||
|
||||
// // ===== Запуск Lua
|
||||
// L := lua.NewState()
|
||||
// defer L.Close()
|
||||
|
||||
// inTable := L.NewTable()
|
||||
// paramsTable := L.NewTable()
|
||||
// if params, ok := req.Payload["PassedParameters"].(map[string]interface{}); ok {
|
||||
// for k, v := range params {
|
||||
// L.SetField(paramsTable, k, utils.ConvertGolangTypesToLua(L, v))
|
||||
// }
|
||||
// }
|
||||
// L.SetField(inTable, "Params", paramsTable)
|
||||
// L.SetGlobal("In", inTable)
|
||||
|
||||
// resultTable := L.NewTable()
|
||||
// outTable := L.NewTable()
|
||||
// L.SetField(outTable, "Result", resultTable)
|
||||
// L.SetGlobal("Out", outTable)
|
||||
|
||||
// prepareLua := filepath.Join(h.cfg.ComDir, "_prepare.lua")
|
||||
// if _, err := os.Stat(prepareLua); err == nil {
|
||||
// if err := L.DoFile(prepareLua); err != nil {
|
||||
// utils.WriteJSONError(w, http.StatusInternalServerError, "lua _prepare ошибка: "+err.Error())
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// if err := L.DoFile(fullPath); err != nil {
|
||||
// utils.WriteJSONError(w, http.StatusInternalServerError, "lua exec ошибка: "+err.Error())
|
||||
// return
|
||||
// }
|
||||
|
||||
// lv := L.GetGlobal("Out")
|
||||
// tbl, ok := lv.(*lua.LTable)
|
||||
// if !ok {
|
||||
// utils.WriteJSONError(w, http.StatusInternalServerError, "'Out' не таблица")
|
||||
// return
|
||||
// }
|
||||
// resultVal := tbl.RawGetString("Result")
|
||||
// resultTbl, ok := resultVal.(*lua.LTable)
|
||||
// if !ok {
|
||||
// utils.WriteJSONError(w, http.StatusInternalServerError, "'Result' не таблица")
|
||||
// return
|
||||
// }
|
||||
|
||||
// out := make(map[string]any)
|
||||
// resultTbl.ForEach(func(key lua.LValue, value lua.LValue) {
|
||||
// out[key.String()] = utils.ConvertLuaTypesToGolang(value)
|
||||
// })
|
||||
|
||||
// uuid32, _ := corestate.GetNodeUUID(filepath.Join(config.MetaDir, "uuid"))
|
||||
|
||||
// resp := PettiResponse{
|
||||
// PettiVer: req.PettiVer,
|
||||
// ResponsibleAgentUUID: uuid32,
|
||||
// PackageType: struct {
|
||||
// AnswerOf string `json:"AnswerOf"`
|
||||
// }{AnswerOf: req.PackageType.Request},
|
||||
// Payload: map[string]any{
|
||||
// "RequestedCommand": cmd,
|
||||
// "Response": out,
|
||||
// },
|
||||
// }
|
||||
|
||||
// // ===== Финальная проверка на сериализацию (валидность сборки)
|
||||
// respData, err := json.Marshal(resp)
|
||||
// if err != nil {
|
||||
// utils.WriteJSONError(w, http.StatusInternalServerError, "внутренняя ошибка: пакет невалиден")
|
||||
// return
|
||||
// }
|
||||
|
||||
// w.Header().Set("Content-Type", "application/json")
|
||||
// w.WriteHeader(http.StatusOK)
|
||||
// if _, err := w.Write(respData); err != nil {
|
||||
// h.log.Error("Ошибка при отправке JSON", slog.String("err", err.Error()))
|
||||
// }
|
||||
|
||||
// // ===== Логгирование статуса
|
||||
// status, _ := out["status"].(string)
|
||||
// switch status {
|
||||
// case "ok":
|
||||
// h.log.Info("Успешно", slog.String("cmd", cmd), slog.Any("out", out))
|
||||
// case "error":
|
||||
// h.log.Warn("Ошибка в команде", slog.String("cmd", cmd), slog.Any("out", out))
|
||||
// default:
|
||||
// h.log.Info("Неизвестный статус", slog.String("cmd", cmd), slog.Any("out", out))
|
||||
// }
|
||||
// }
|
||||
|
||||
/*
|
||||
import (
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/core/config"
|
||||
"github.com/akyaiy/GoSally-mvp/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/core/utils"
|
||||
"github.com/go-chi/chi/v5"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
// HandlerV1 is the main handler for version 1 of the API.
|
||||
// The function processes the HTTP request and runs Lua scripts,
|
||||
// preparing the environment and subsequently transmitting the execution result
|
||||
func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
uuid16, err := utils.NewUUID(int(config.UUIDLength))
|
||||
method, err := h.resolveMethodPath(req.Method)
|
||||
if err != nil {
|
||||
h.log.Error("Failed to generate UUID",
|
||||
slog.String("error", err.Error()))
|
||||
|
||||
if err := utils.WriteJSONError(w, http.StatusInternalServerError, "failed to generate UUID: "+err.Error()); err != nil {
|
||||
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
log := h.log.With(
|
||||
slog.Group("request",
|
||||
slog.String("version", h.GetVersion()),
|
||||
slog.String("url", r.URL.String()),
|
||||
slog.String("method", r.Method),
|
||||
),
|
||||
slog.Group("connection",
|
||||
slog.String("connection-uuid", uuid16),
|
||||
slog.String("remote", r.RemoteAddr),
|
||||
),
|
||||
)
|
||||
log.Info("Received request")
|
||||
|
||||
cmd := chi.URLParam(r, "cmd")
|
||||
if !h.allowedCmd.MatchString(string([]rune(cmd)[0])) || !h.listAllowedCmd.MatchString(cmd) {
|
||||
log.Error("HTTP request error",
|
||||
slog.String("error", "invalid command"),
|
||||
slog.String("cmd", cmd),
|
||||
slog.Int("status", http.StatusBadRequest))
|
||||
|
||||
if err := utils.WriteJSONError(w, http.StatusBadRequest, "invalid command"); err != nil {
|
||||
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
scriptPath := h.comMatch(chi.URLParam(r, "ver"), cmd)
|
||||
if scriptPath == "" {
|
||||
log.Error("HTTP request error",
|
||||
slog.String("error", "command not found"),
|
||||
slog.String("cmd", cmd),
|
||||
slog.Int("status", http.StatusNotFound))
|
||||
|
||||
if err := utils.WriteJSONError(w, http.StatusNotFound, "command not found"); err != nil {
|
||||
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
scriptPath = filepath.Join(h.cfg.ComDir, scriptPath)
|
||||
if _, err := os.Stat(scriptPath); err != nil {
|
||||
log.Error("HTTP request error",
|
||||
slog.String("error", "command not found"),
|
||||
slog.String("cmd", cmd),
|
||||
slog.Int("status", http.StatusNotFound))
|
||||
|
||||
if err := utils.WriteJSONError(w, http.StatusNotFound, "command not found"); err != nil {
|
||||
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
L := lua.NewState()
|
||||
defer L.Close()
|
||||
|
||||
paramsTable := L.NewTable()
|
||||
qt := r.URL.Query()
|
||||
for k, v := range qt {
|
||||
if len(v) > 0 {
|
||||
L.SetField(paramsTable, k, lua.LString(v[0]))
|
||||
if err.Error() == rpc.ErrInvalidMethodFormatS {
|
||||
h.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrInvalidMethodFormatS), slog.String("requested-method", req.Method))
|
||||
return rpc.NewError(rpc.ErrInvalidMethodFormat, rpc.ErrInvalidMethodFormatS, nil, req.ID)
|
||||
} else if err.Error() == rpc.ErrMethodNotFoundS {
|
||||
h.x.SLog.Info("invalid request received", slog.String("issue", rpc.ErrMethodNotFoundS), slog.String("requested-method", req.Method))
|
||||
return rpc.NewError(rpc.ErrMethodNotFound, rpc.ErrMethodNotFoundS, nil, req.ID)
|
||||
}
|
||||
}
|
||||
inTable := L.NewTable()
|
||||
L.SetField(inTable, "Params", paramsTable)
|
||||
L.SetGlobal("In", inTable)
|
||||
|
||||
// Создаем таблицу Out с Result
|
||||
resultTable := L.NewTable()
|
||||
outTable := L.NewTable()
|
||||
L.SetField(outTable, "Result", resultTable)
|
||||
L.SetGlobal("Out", outTable)
|
||||
|
||||
prepareLuaEnv := filepath.Join(h.cfg.ComDir, "_prepare.lua")
|
||||
if _, err := os.Stat(prepareLuaEnv); err == nil {
|
||||
if err := L.DoFile(prepareLuaEnv); err != nil {
|
||||
log.Error("Failed to prepare lua environment",
|
||||
slog.String("error", err.Error()))
|
||||
|
||||
if err := utils.WriteJSONError(w, http.StatusInternalServerError, "lua error: "+err.Error()); err != nil {
|
||||
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
} else {
|
||||
log.Warn("No environment preparation script found, skipping preparation")
|
||||
}
|
||||
|
||||
if err := L.DoFile(scriptPath); err != nil {
|
||||
log.Error("Failed to execute lua script",
|
||||
slog.String("error", err.Error()))
|
||||
if err := utils.WriteJSONError(w, http.StatusInternalServerError, "lua error: "+err.Error()); err != nil {
|
||||
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
lv := L.GetGlobal("Out")
|
||||
tbl, ok := lv.(*lua.LTable)
|
||||
if !ok {
|
||||
log.Error("Lua global 'Out' is not a table")
|
||||
|
||||
if err := utils.WriteJSONError(w, http.StatusInternalServerError, "'Out' is not a table"); err != nil {
|
||||
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
resultVal := tbl.RawGetString("Result")
|
||||
resultTbl, ok := resultVal.(*lua.LTable)
|
||||
if !ok {
|
||||
log.Error("Lua global 'Result' is not a table")
|
||||
|
||||
if err := utils.WriteJSONError(w, http.StatusInternalServerError, "'Result' is not a table"); err != nil {
|
||||
h.log.Error("Failed to write JSON", slog.String("err", err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
out := make(map[string]any)
|
||||
resultTbl.ForEach(func(key lua.LValue, value lua.LValue) {
|
||||
out[key.String()] = utils.ConvertLuaTypesToGolang(value)
|
||||
})
|
||||
uuid32, _ := corestate.GetNodeUUID(filepath.Join(config.MetaDir, "uuid"))
|
||||
response := ResponseFormat{
|
||||
ResponsibleAgentUUID: uuid32,
|
||||
RequestedCommand: cmd,
|
||||
Response: out,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
log.Error("Failed to encode JSON response",
|
||||
slog.String("error", err.Error()))
|
||||
}
|
||||
|
||||
status, _ := out["status"].(string)
|
||||
switch status {
|
||||
case "error":
|
||||
log.Info("Command executed with error",
|
||||
slog.String("cmd", cmd),
|
||||
slog.Any("result", out))
|
||||
case "ok":
|
||||
log.Info("Command executed successfully",
|
||||
slog.String("cmd", cmd),
|
||||
slog.Any("result", out))
|
||||
default:
|
||||
log.Info("Command executed and returned an unknown status",
|
||||
slog.String("cmd", cmd),
|
||||
slog.Any("result", out))
|
||||
}
|
||||
|
||||
log.Info("Session completed")
|
||||
return h.handleLUA(sid, r, req, method)
|
||||
}
|
||||
*/
|
||||
|
||||
653
internal/server/sv1/lua_handler.go
Normal file
653
internal/server/sv1/lua_handler.go
Normal file
@@ -0,0 +1,653 @@
|
||||
package sv1
|
||||
|
||||
// TODO: make a lua state pool using sync.Pool
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"math/rand/v2"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/colors"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
type DBConnection struct {
|
||||
dbPath string
|
||||
log bool
|
||||
logger *slog.Logger
|
||||
writeChan chan *dbWriteRequest
|
||||
closeChan chan struct{}
|
||||
}
|
||||
|
||||
type dbWriteRequest struct {
|
||||
query string
|
||||
args []interface{}
|
||||
resCh chan *dbWriteResult
|
||||
}
|
||||
|
||||
type dbWriteResult struct {
|
||||
rowsAffected int64
|
||||
err error
|
||||
}
|
||||
|
||||
var dbMutexMap = make(map[string]*sync.RWMutex)
|
||||
var dbGlobalMutex sync.Mutex
|
||||
|
||||
func getDBMutex(dbPath string) *sync.RWMutex {
|
||||
dbGlobalMutex.Lock()
|
||||
defer dbGlobalMutex.Unlock()
|
||||
|
||||
if mtx, ok := dbMutexMap[dbPath]; ok {
|
||||
return mtx
|
||||
}
|
||||
|
||||
mtx := &sync.RWMutex{}
|
||||
dbMutexMap[dbPath] = mtx
|
||||
return mtx
|
||||
}
|
||||
|
||||
func loadDBMod(llog *slog.Logger) func(*lua.LState) int {
|
||||
llog.Debug("import module db-sqlite")
|
||||
return func(L *lua.LState) int {
|
||||
dbMod := L.NewTable()
|
||||
|
||||
L.SetField(dbMod, "connect", L.NewFunction(func(L *lua.LState) int {
|
||||
dbPath := L.CheckString(1)
|
||||
|
||||
logQueries := false
|
||||
if L.GetTop() >= 2 {
|
||||
opts := L.CheckTable(2)
|
||||
if val := opts.RawGetString("log"); val != lua.LNil {
|
||||
logQueries = lua.LVAsBool(val)
|
||||
}
|
||||
}
|
||||
|
||||
conn := &DBConnection{
|
||||
dbPath: dbPath,
|
||||
log: logQueries,
|
||||
logger: llog,
|
||||
writeChan: make(chan *dbWriteRequest, 100),
|
||||
closeChan: make(chan struct{}),
|
||||
}
|
||||
|
||||
go conn.processWrites()
|
||||
|
||||
ud := L.NewUserData()
|
||||
ud.Value = conn
|
||||
L.SetMetatable(ud, L.GetTypeMetatable("gosally_db"))
|
||||
|
||||
L.Push(ud)
|
||||
return 1
|
||||
}))
|
||||
|
||||
mt := L.NewTypeMetatable("gosally_db")
|
||||
L.SetField(mt, "__index", L.SetFuncs(L.NewTable(), map[string]lua.LGFunction{
|
||||
"exec": dbExec,
|
||||
"query": dbQuery,
|
||||
"close": dbClose,
|
||||
}))
|
||||
|
||||
L.SetField(dbMod, "__gosally_internal", lua.LString("0"))
|
||||
L.Push(dbMod)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *DBConnection) processWrites() {
|
||||
for {
|
||||
select {
|
||||
case req := <-conn.writeChan:
|
||||
mtx := getDBMutex(conn.dbPath)
|
||||
mtx.Lock()
|
||||
|
||||
db, err := sql.Open("sqlite", conn.dbPath+"?_busy_timeout=5000&_journal_mode=WAL&_sync=NORMAL&_cache_size=-10000")
|
||||
if err == nil {
|
||||
_, err = db.Exec("PRAGMA journal_mode=WAL;")
|
||||
if err == nil {
|
||||
res, execErr := db.Exec(req.query, req.args...)
|
||||
if execErr == nil {
|
||||
rows, _ := res.RowsAffected()
|
||||
req.resCh <- &dbWriteResult{rowsAffected: rows}
|
||||
} else {
|
||||
req.resCh <- &dbWriteResult{err: execErr}
|
||||
}
|
||||
}
|
||||
db.Close()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
req.resCh <- &dbWriteResult{err: err}
|
||||
}
|
||||
|
||||
mtx.Unlock()
|
||||
case <-conn.closeChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func dbExec(L *lua.LState) int {
|
||||
ud := L.CheckUserData(1)
|
||||
conn, ok := ud.Value.(*DBConnection)
|
||||
if !ok {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString("invalid database connection"))
|
||||
return 2
|
||||
}
|
||||
|
||||
query := L.CheckString(2)
|
||||
|
||||
var args []any
|
||||
if L.GetTop() >= 3 {
|
||||
params := L.CheckTable(3)
|
||||
params.ForEach(func(k lua.LValue, v lua.LValue) {
|
||||
args = append(args, ConvertLuaTypesToGolang(v))
|
||||
})
|
||||
}
|
||||
|
||||
if conn.log {
|
||||
conn.logger.Info("DB Exec",
|
||||
slog.String("query", query),
|
||||
slog.Any("params", args))
|
||||
}
|
||||
|
||||
resCh := make(chan *dbWriteResult, 1)
|
||||
conn.writeChan <- &dbWriteRequest{
|
||||
query: query,
|
||||
args: args,
|
||||
resCh: resCh,
|
||||
}
|
||||
|
||||
ctx := L.NewTable()
|
||||
L.SetField(ctx, "done", lua.LBool(false))
|
||||
|
||||
var result lua.LValue = lua.LNil
|
||||
var errorMsg lua.LValue = lua.LNil
|
||||
|
||||
L.SetField(ctx, "wait", L.NewFunction(func(lL *lua.LState) int {
|
||||
res := <-resCh
|
||||
L.SetField(ctx, "done", lua.LBool(true))
|
||||
|
||||
if res.err != nil {
|
||||
errorMsg = lua.LString(res.err.Error())
|
||||
result = lua.LNil
|
||||
} else {
|
||||
result = lua.LNumber(res.rowsAffected)
|
||||
errorMsg = lua.LNil
|
||||
}
|
||||
|
||||
if res.err != nil {
|
||||
lL.Push(lua.LNil)
|
||||
lL.Push(lua.LString(res.err.Error()))
|
||||
return 2
|
||||
}
|
||||
lL.Push(lua.LNumber(res.rowsAffected))
|
||||
lL.Push(lua.LNil)
|
||||
return 2
|
||||
}))
|
||||
|
||||
L.SetField(ctx, "check", L.NewFunction(func(lL *lua.LState) int {
|
||||
select {
|
||||
case res := <-resCh:
|
||||
lL.SetField(ctx, "done", lua.LBool(true))
|
||||
if res.err != nil {
|
||||
errorMsg = lua.LString(res.err.Error())
|
||||
result = lua.LNil
|
||||
lL.Push(lua.LNil)
|
||||
lL.Push(lua.LString(res.err.Error()))
|
||||
return 2
|
||||
} else {
|
||||
result = lua.LNumber(res.rowsAffected)
|
||||
errorMsg = lua.LNil
|
||||
lL.Push(lua.LNumber(res.rowsAffected))
|
||||
lL.Push(lua.LNil)
|
||||
return 2
|
||||
}
|
||||
default:
|
||||
lL.Push(result)
|
||||
lL.Push(errorMsg)
|
||||
return 2
|
||||
}
|
||||
}))
|
||||
|
||||
L.Push(ctx)
|
||||
L.Push(lua.LNil)
|
||||
return 2
|
||||
}
|
||||
|
||||
func dbQuery(L *lua.LState) int {
|
||||
ud := L.CheckUserData(1)
|
||||
conn, ok := ud.Value.(*DBConnection)
|
||||
if !ok {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString("invalid database connection"))
|
||||
return 2
|
||||
}
|
||||
|
||||
query := L.CheckString(2)
|
||||
|
||||
var args []any
|
||||
if L.GetTop() >= 3 {
|
||||
params := L.CheckTable(3)
|
||||
params.ForEach(func(k lua.LValue, v lua.LValue) {
|
||||
args = append(args, ConvertLuaTypesToGolang(v))
|
||||
})
|
||||
}
|
||||
|
||||
if conn.log {
|
||||
conn.logger.Info("DB Query",
|
||||
slog.String("query", query),
|
||||
slog.Any("params", args))
|
||||
}
|
||||
|
||||
mtx := getDBMutex(conn.dbPath)
|
||||
mtx.RLock()
|
||||
defer mtx.RUnlock()
|
||||
|
||||
db, err := sql.Open("sqlite", conn.dbPath+"?_busy_timeout=5000&_journal_mode=WAL&_sync=NORMAL&_cache_size=-10000")
|
||||
if err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
rows, err := db.Query(query, args...)
|
||||
if err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(fmt.Sprintf("query failed: %v", err)))
|
||||
return 2
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
columns, err := rows.Columns()
|
||||
if err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(fmt.Sprintf("get columns failed: %v", err)))
|
||||
return 2
|
||||
}
|
||||
|
||||
result := L.NewTable()
|
||||
colCount := len(columns)
|
||||
values := make([]any, colCount)
|
||||
valuePtrs := make([]any, colCount)
|
||||
|
||||
for rows.Next() {
|
||||
for i := range columns {
|
||||
valuePtrs[i] = &values[i]
|
||||
}
|
||||
|
||||
if err := rows.Scan(valuePtrs...); err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(fmt.Sprintf("scan failed: %v", err)))
|
||||
return 2
|
||||
}
|
||||
|
||||
rowTable := L.NewTable()
|
||||
for i, col := range columns {
|
||||
val := values[i]
|
||||
if val == nil {
|
||||
L.SetField(rowTable, col, lua.LNil)
|
||||
} else {
|
||||
L.SetField(rowTable, col, ConvertGolangTypesToLua(L, val))
|
||||
}
|
||||
}
|
||||
result.Append(rowTable)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(fmt.Sprintf("rows iteration failed: %v", err)))
|
||||
return 2
|
||||
}
|
||||
|
||||
L.Push(result)
|
||||
return 1
|
||||
}
|
||||
|
||||
func dbClose(L *lua.LState) int {
|
||||
ud := L.CheckUserData(1)
|
||||
conn, ok := ud.Value.(*DBConnection)
|
||||
if !ok {
|
||||
L.Push(lua.LFalse)
|
||||
L.Push(lua.LString("invalid database connection"))
|
||||
return 2
|
||||
}
|
||||
|
||||
close(conn.closeChan)
|
||||
L.Push(lua.LTrue)
|
||||
return 1
|
||||
}
|
||||
|
||||
func addInitiatorHeaders(sid string, req *http.Request, headers http.Header) {
|
||||
clientIP := req.RemoteAddr
|
||||
if forwardedFor := req.Header.Get("X-Forwarded-For"); forwardedFor != "" {
|
||||
clientIP = forwardedFor
|
||||
}
|
||||
headers.Set("X-Initiator-IP", clientIP)
|
||||
headers.Set("X-Session-UUID", sid)
|
||||
headers.Set("X-Initiator-Host", req.Host)
|
||||
headers.Set("X-Initiator-User-Agent", req.UserAgent())
|
||||
headers.Set("X-Initiator-Referer", req.Referer())
|
||||
}
|
||||
|
||||
// A small reminder: this code is only at the MVP stage,
|
||||
// and some parts of the code may cause shock from the
|
||||
// incompetence of the developer. But, in the end,
|
||||
// this code is just an idea. If there is a desire to
|
||||
// contribute to the development of the code,
|
||||
// I will be only glad.
|
||||
// TODO: make this huge function more harmonious by dividing responsibilities
|
||||
func (h *HandlerV1) handleLUA(sid string, r *http.Request, req *rpc.RPCRequest, path string) *rpc.RPCResponse {
|
||||
llog := h.x.SLog.With(slog.String("session-id", sid))
|
||||
llog.Debug("handling LUA")
|
||||
L := lua.NewState()
|
||||
defer L.Close()
|
||||
|
||||
seed := rand.Int()
|
||||
|
||||
loadSessionMod := func(lL *lua.LState) int {
|
||||
llog.Debug("import module session", slog.String("script", path))
|
||||
sessionMod := lL.NewTable()
|
||||
inTable := lL.NewTable()
|
||||
paramsTable := lL.NewTable()
|
||||
if fetchedParams, ok := req.Params.(map[string]any); ok {
|
||||
for k, v := range fetchedParams {
|
||||
lL.SetField(paramsTable, k, ConvertGolangTypesToLua(lL, v))
|
||||
}
|
||||
}
|
||||
lL.SetField(inTable, "params", paramsTable)
|
||||
|
||||
outTable := lL.NewTable()
|
||||
resultTable := lL.NewTable()
|
||||
lL.SetField(outTable, "result", resultTable)
|
||||
|
||||
lL.SetField(inTable, "address", lua.LString(r.RemoteAddr))
|
||||
lL.SetField(sessionMod, "request", inTable)
|
||||
lL.SetField(sessionMod, "response", outTable)
|
||||
|
||||
lL.SetField(sessionMod, "id", lua.LString(sid))
|
||||
|
||||
lL.SetField(sessionMod, "__gosally_internal", lua.LString(fmt.Sprint(seed)))
|
||||
lL.Push(sessionMod)
|
||||
return 1
|
||||
}
|
||||
|
||||
loadLogMod := func(lL *lua.LState) int {
|
||||
llog.Debug("import module log", slog.String("script", path))
|
||||
logMod := lL.NewTable()
|
||||
|
||||
logFuncs := map[string]func(string, ...any){
|
||||
"info": llog.Info,
|
||||
"debug": llog.Debug,
|
||||
"error": llog.Error,
|
||||
"warn": llog.Warn,
|
||||
}
|
||||
|
||||
for name, logFunc := range logFuncs {
|
||||
fun := logFunc
|
||||
lL.SetField(logMod, name, lL.NewFunction(func(lL *lua.LState) int {
|
||||
msg := lL.Get(1)
|
||||
converted := ConvertLuaTypesToGolang(msg)
|
||||
fun(fmt.Sprintf("the script says: %s", converted), slog.String("script", path))
|
||||
return 0
|
||||
}))
|
||||
}
|
||||
|
||||
for _, fn := range []struct {
|
||||
field string
|
||||
pfunc func(string, ...any)
|
||||
color func() string
|
||||
}{
|
||||
{"event", h.x.Log.Printf, nil},
|
||||
{"event_error", h.x.Log.Printf, colors.PrintError},
|
||||
{"event_warn", h.x.Log.Printf, colors.PrintWarn},
|
||||
} {
|
||||
lL.SetField(logMod, fn.field, lL.NewFunction(func(lL *lua.LState) int {
|
||||
msg := lL.Get(1)
|
||||
converted := ConvertLuaTypesToGolang(msg)
|
||||
if fn.color != nil {
|
||||
h.x.Log.Printf("%s: %s: %s", fn.color(), path, converted)
|
||||
} else {
|
||||
h.x.Log.Printf("%s: %s", path, converted)
|
||||
}
|
||||
return 0
|
||||
}))
|
||||
}
|
||||
|
||||
lL.SetField(logMod, "__gosally_internal", lua.LString(fmt.Sprint(seed)))
|
||||
lL.Push(logMod)
|
||||
return 1
|
||||
}
|
||||
|
||||
loadNetMod := func(lL *lua.LState) int {
|
||||
llog.Debug("import module net", slog.String("script", path))
|
||||
netMod := lL.NewTable()
|
||||
netModhttp := lL.NewTable()
|
||||
|
||||
lL.SetField(netModhttp, "get_request", lL.NewFunction(func(lL *lua.LState) int {
|
||||
logRequest := lL.ToBool(1)
|
||||
url := lL.ToString(2)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
lL.Push(lua.LNil)
|
||||
lL.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
|
||||
addInitiatorHeaders(sid, r, req.Header)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
lL.Push(lua.LNil)
|
||||
lL.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
lL.Push(lua.LNil)
|
||||
lL.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
|
||||
if logRequest {
|
||||
llog.Info("HTTP GET request",
|
||||
slog.String("script", path),
|
||||
slog.String("url", url),
|
||||
slog.Int("status", resp.StatusCode),
|
||||
slog.String("status_text", resp.Status),
|
||||
slog.String("initiator_ip", req.Header.Get("X-Initiator-IP")),
|
||||
)
|
||||
}
|
||||
|
||||
result := lL.NewTable()
|
||||
lL.SetField(result, "status", lua.LNumber(resp.StatusCode))
|
||||
lL.SetField(result, "status_text", lua.LString(resp.Status))
|
||||
lL.SetField(result, "body", lua.LString(body))
|
||||
lL.SetField(result, "content_length", lua.LNumber(resp.ContentLength))
|
||||
|
||||
headers := lL.NewTable()
|
||||
for k, v := range resp.Header {
|
||||
lL.SetField(headers, k, ConvertGolangTypesToLua(lL, v))
|
||||
}
|
||||
lL.SetField(result, "headers", headers)
|
||||
|
||||
lL.Push(result)
|
||||
return 1
|
||||
}))
|
||||
|
||||
lL.SetField(netModhttp, "post_request", lL.NewFunction(func(lL *lua.LState) int {
|
||||
logRequest := lL.ToBool(1)
|
||||
url := lL.ToString(2)
|
||||
contentType := lL.ToString(3)
|
||||
payload := lL.ToString(4)
|
||||
|
||||
body := strings.NewReader(payload)
|
||||
|
||||
req, err := http.NewRequest("POST", url, body)
|
||||
if err != nil {
|
||||
lL.Push(lua.LNil)
|
||||
lL.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
|
||||
addInitiatorHeaders(sid, r, req.Header)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
lL.Push(lua.LNil)
|
||||
lL.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
lL.Push(lua.LNil)
|
||||
lL.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
|
||||
if logRequest {
|
||||
llog.Info("HTTP POST request",
|
||||
slog.String("script", path),
|
||||
slog.String("url", url),
|
||||
slog.String("content_type", contentType),
|
||||
slog.Int("status", resp.StatusCode),
|
||||
slog.String("status_text", resp.Status),
|
||||
slog.String("initiator_ip", req.Header.Get("X-Initiator-IP")),
|
||||
)
|
||||
}
|
||||
|
||||
result := lL.NewTable()
|
||||
lL.SetField(result, "status", lua.LNumber(resp.StatusCode))
|
||||
lL.SetField(result, "status_text", lua.LString(resp.Status))
|
||||
lL.SetField(result, "body", lua.LString(respBody))
|
||||
lL.SetField(result, "content_length", lua.LNumber(resp.ContentLength))
|
||||
|
||||
headers := lL.NewTable()
|
||||
for k, v := range resp.Header {
|
||||
lL.SetField(headers, k, ConvertGolangTypesToLua(lL, v))
|
||||
}
|
||||
lL.SetField(result, "headers", headers)
|
||||
|
||||
lL.Push(result)
|
||||
return 1
|
||||
}))
|
||||
|
||||
lL.SetField(netMod, "http", netModhttp)
|
||||
|
||||
lL.SetField(netMod, "__gosally_internal", lua.LString(fmt.Sprint(seed)))
|
||||
lL.Push(netMod)
|
||||
return 1
|
||||
}
|
||||
|
||||
L.PreloadModule("internal.session", loadSessionMod)
|
||||
L.PreloadModule("internal.log", loadLogMod)
|
||||
L.PreloadModule("internal.net", loadNetMod)
|
||||
L.PreloadModule("internal.database-sqlite", loadDBMod(llog))
|
||||
|
||||
llog.Debug("preparing environment")
|
||||
prep := filepath.Join(*h.x.Config.Conf.Node.ComDir, "_prepare.lua")
|
||||
if _, err := os.Stat(prep); err == nil {
|
||||
if err := L.DoFile(prep); err != nil {
|
||||
llog.Error("script error", slog.String("script", path), slog.String("error", err.Error()))
|
||||
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, nil, req.ID)
|
||||
}
|
||||
}
|
||||
llog.Debug("executing script", slog.String("script", path))
|
||||
if err := L.DoFile(path); err != nil {
|
||||
llog.Error("script error", slog.String("script", path), slog.String("error", err.Error()))
|
||||
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, nil, req.ID)
|
||||
}
|
||||
|
||||
pkg := L.GetGlobal("package")
|
||||
pkgTbl, ok := pkg.(*lua.LTable)
|
||||
if !ok {
|
||||
llog.Error("script error", slog.String("script", path), slog.String("error", "package not found"))
|
||||
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, nil, req.ID)
|
||||
}
|
||||
|
||||
loaded := pkgTbl.RawGetString("loaded")
|
||||
loadedTbl, ok := loaded.(*lua.LTable)
|
||||
if !ok {
|
||||
llog.Error("script error", slog.String("script", path), slog.String("error", "package.loaded not found"))
|
||||
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, nil, req.ID)
|
||||
}
|
||||
|
||||
sessionVal := loadedTbl.RawGetString("internal.session")
|
||||
sessionTbl, ok := sessionVal.(*lua.LTable)
|
||||
if !ok {
|
||||
return rpc.NewResponse(map[string]any{
|
||||
"responsible-node": h.cs.UUID32,
|
||||
}, req.ID)
|
||||
}
|
||||
|
||||
tag := sessionTbl.RawGetString("__gosally_internal")
|
||||
if tag.Type() != lua.LTString || tag.String() != fmt.Sprint(seed) {
|
||||
llog.Debug("stock session module is not imported: wrong seed", slog.String("script", path))
|
||||
return rpc.NewResponse(map[string]any{
|
||||
"responsible-node": h.cs.UUID32,
|
||||
}, req.ID)
|
||||
}
|
||||
|
||||
outVal := sessionTbl.RawGetString("response")
|
||||
outTbl, ok := outVal.(*lua.LTable)
|
||||
if !ok {
|
||||
llog.Error("script error", slog.String("script", path), slog.String("error", "response is not a table"))
|
||||
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, nil, req.ID)
|
||||
}
|
||||
|
||||
if errVal := outTbl.RawGetString("error"); errVal != lua.LNil {
|
||||
llog.Debug("catch error table", slog.String("script", path))
|
||||
if errTbl, ok := errVal.(*lua.LTable); ok {
|
||||
code := rpc.ErrInternalError
|
||||
message := rpc.ErrInternalErrorS
|
||||
data := make(map[string]any)
|
||||
if c := errTbl.RawGetString("code"); c.Type() == lua.LTNumber {
|
||||
code = int(c.(lua.LNumber))
|
||||
}
|
||||
if msg := errTbl.RawGetString("message"); msg.Type() == lua.LTString {
|
||||
message = msg.String()
|
||||
}
|
||||
rawData := errTbl.RawGetString("data")
|
||||
|
||||
if tbl, ok := rawData.(*lua.LTable); ok {
|
||||
tbl.ForEach(func(k, v lua.LValue) { data[k.String()] = ConvertLuaTypesToGolang(v) })
|
||||
} else {
|
||||
llog.Error("the script terminated with an error", slog.String("code", strconv.Itoa(code)), slog.String("message", message))
|
||||
return rpc.NewError(code, message, ConvertLuaTypesToGolang(rawData), req.ID)
|
||||
}
|
||||
llog.Error("the script terminated with an error", slog.String("code", strconv.Itoa(code)), slog.String("message", message))
|
||||
return rpc.NewError(code, message, data, req.ID)
|
||||
}
|
||||
return rpc.NewError(rpc.ErrInternalError, rpc.ErrInternalErrorS, nil, req.ID)
|
||||
}
|
||||
|
||||
resultVal := outTbl.RawGetString("result")
|
||||
payload := make(map[string]any)
|
||||
if tbl, ok := resultVal.(*lua.LTable); ok {
|
||||
tbl.ForEach(func(k, v lua.LValue) { payload[k.String()] = ConvertLuaTypesToGolang(v) })
|
||||
} else {
|
||||
payload["message"] = ConvertLuaTypesToGolang(resultVal)
|
||||
}
|
||||
payload["responsible-node"] = h.cs.UUID32
|
||||
return rpc.NewResponse(payload, req.ID)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package utils
|
||||
package sv1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -17,7 +17,6 @@ func ConvertLuaTypesToGolang(value lua.LValue) any {
|
||||
case lua.LTTable:
|
||||
tbl := value.(*lua.LTable)
|
||||
|
||||
// Попробуем как массив
|
||||
var arr []any
|
||||
isArray := true
|
||||
tbl.ForEach(func(key, val lua.LValue) {
|
||||
26
internal/server/sv1/path.go
Normal file
26
internal/server/sv1/path.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package sv1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
||||
)
|
||||
|
||||
func (h *HandlerV1) resolveMethodPath(method string) (string, error) {
|
||||
if !h.allowedCmd.MatchString(method) {
|
||||
return "", errors.New(rpc.ErrInvalidMethodFormatS)
|
||||
}
|
||||
|
||||
parts := strings.Split(method, ">")
|
||||
relPath := filepath.Join(parts...) + ".lua"
|
||||
fullPath := filepath.Join(*h.x.Config.Conf.Node.ComDir, relPath)
|
||||
|
||||
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
|
||||
return "", errors.New(rpc.ErrMethodNotFoundS)
|
||||
}
|
||||
|
||||
return fullPath, nil
|
||||
}
|
||||
@@ -3,30 +3,27 @@
|
||||
package sv1
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"regexp"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
|
||||
)
|
||||
|
||||
// HandlerV1InitStruct structure is only for initialization
|
||||
type HandlerV1InitStruct struct {
|
||||
Ver string
|
||||
Log slog.Logger
|
||||
Config *config.Conf
|
||||
AllowedCmd *regexp.Regexp
|
||||
ListAllowedCmd *regexp.Regexp
|
||||
Ver string
|
||||
CS *corestate.CoreState
|
||||
X *app.AppX
|
||||
AllowedCmd *regexp.Regexp
|
||||
}
|
||||
|
||||
// HandlerV1 implements the ServerV1UtilsContract and serves as the main handler for API requests.
|
||||
type HandlerV1 struct {
|
||||
log *slog.Logger
|
||||
|
||||
cfg *config.Conf
|
||||
cs *corestate.CoreState
|
||||
x *app.AppX
|
||||
|
||||
// allowedCmd and listAllowedCmd are regular expressions used to validate command names.
|
||||
allowedCmd *regexp.Regexp
|
||||
listAllowedCmd *regexp.Regexp
|
||||
allowedCmd *regexp.Regexp
|
||||
|
||||
ver string
|
||||
}
|
||||
@@ -36,11 +33,10 @@ type HandlerV1 struct {
|
||||
// because there is no validation of parameters in this function.
|
||||
func InitV1Server(o *HandlerV1InitStruct) *HandlerV1 {
|
||||
return &HandlerV1{
|
||||
log: &o.Log,
|
||||
cfg: o.Config,
|
||||
allowedCmd: o.AllowedCmd,
|
||||
listAllowedCmd: o.ListAllowedCmd,
|
||||
ver: o.Ver,
|
||||
cs: o.CS,
|
||||
x: o.X,
|
||||
allowedCmd: o.AllowedCmd,
|
||||
ver: o.Ver,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package sv1
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
)
|
||||
|
||||
func (h *HandlerV1) comMatch(ver string, comName string) string {
|
||||
files, err := os.ReadDir(h.cfg.ComDir)
|
||||
if err != nil {
|
||||
h.log.Error("Failed to read com dir",
|
||||
slog.String("error", err.Error()))
|
||||
return ""
|
||||
}
|
||||
|
||||
baseName := comName + ".lua"
|
||||
verName := comName + "?" + ver + ".lua"
|
||||
|
||||
var baseFileFound string
|
||||
|
||||
for _, f := range files {
|
||||
if f.IsDir() {
|
||||
continue
|
||||
}
|
||||
fname := f.Name()
|
||||
|
||||
if fname == verName {
|
||||
return fname
|
||||
}
|
||||
|
||||
if fname == baseName {
|
||||
baseFileFound = fname
|
||||
}
|
||||
}
|
||||
|
||||
return baseFileFound
|
||||
}
|
||||
1
main.go
1
main.go
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/akyaiy/GoSally-mvp/cmd"
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
21
vendor/github.com/dustin/go-humanize/.travis.yml
generated
vendored
Normal file
21
vendor/github.com/dustin/go-humanize/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
sudo: false
|
||||
language: go
|
||||
go_import_path: github.com/dustin/go-humanize
|
||||
go:
|
||||
- 1.13.x
|
||||
- 1.14.x
|
||||
- 1.15.x
|
||||
- 1.16.x
|
||||
- stable
|
||||
- master
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: master
|
||||
fast_finish: true
|
||||
install:
|
||||
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
|
||||
script:
|
||||
- diff -u <(echo -n) <(gofmt -d -s .)
|
||||
- go vet .
|
||||
- go install -v -race ./...
|
||||
- go test -v -race ./...
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2017-2021 Uber Technologies, Inc.
|
||||
Copyright (c) 2005-2008 Dustin Sallings <dustin@spy.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -15,5 +15,7 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
<http://www.opensource.org/licenses/mit-license.php>
|
||||
124
vendor/github.com/dustin/go-humanize/README.markdown
generated
vendored
Normal file
124
vendor/github.com/dustin/go-humanize/README.markdown
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
# Humane Units [](https://travis-ci.org/dustin/go-humanize) [](https://godoc.org/github.com/dustin/go-humanize)
|
||||
|
||||
Just a few functions for helping humanize times and sizes.
|
||||
|
||||
`go get` it as `github.com/dustin/go-humanize`, import it as
|
||||
`"github.com/dustin/go-humanize"`, use it as `humanize`.
|
||||
|
||||
See [godoc](https://pkg.go.dev/github.com/dustin/go-humanize) for
|
||||
complete documentation.
|
||||
|
||||
## Sizes
|
||||
|
||||
This lets you take numbers like `82854982` and convert them to useful
|
||||
strings like, `83 MB` or `79 MiB` (whichever you prefer).
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
fmt.Printf("That file is %s.", humanize.Bytes(82854982)) // That file is 83 MB.
|
||||
```
|
||||
|
||||
## Times
|
||||
|
||||
This lets you take a `time.Time` and spit it out in relative terms.
|
||||
For example, `12 seconds ago` or `3 days from now`.
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
fmt.Printf("This was touched %s.", humanize.Time(someTimeInstance)) // This was touched 7 hours ago.
|
||||
```
|
||||
|
||||
Thanks to Kyle Lemons for the time implementation from an IRC
|
||||
conversation one day. It's pretty neat.
|
||||
|
||||
## Ordinals
|
||||
|
||||
From a [mailing list discussion][odisc] where a user wanted to be able
|
||||
to label ordinals.
|
||||
|
||||
0 -> 0th
|
||||
1 -> 1st
|
||||
2 -> 2nd
|
||||
3 -> 3rd
|
||||
4 -> 4th
|
||||
[...]
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
fmt.Printf("You're my %s best friend.", humanize.Ordinal(193)) // You are my 193rd best friend.
|
||||
```
|
||||
|
||||
## Commas
|
||||
|
||||
Want to shove commas into numbers? Be my guest.
|
||||
|
||||
0 -> 0
|
||||
100 -> 100
|
||||
1000 -> 1,000
|
||||
1000000000 -> 1,000,000,000
|
||||
-100000 -> -100,000
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
fmt.Printf("You owe $%s.\n", humanize.Comma(6582491)) // You owe $6,582,491.
|
||||
```
|
||||
|
||||
## Ftoa
|
||||
|
||||
Nicer float64 formatter that removes trailing zeros.
|
||||
|
||||
```go
|
||||
fmt.Printf("%f", 2.24) // 2.240000
|
||||
fmt.Printf("%s", humanize.Ftoa(2.24)) // 2.24
|
||||
fmt.Printf("%f", 2.0) // 2.000000
|
||||
fmt.Printf("%s", humanize.Ftoa(2.0)) // 2
|
||||
```
|
||||
|
||||
## SI notation
|
||||
|
||||
Format numbers with [SI notation][sinotation].
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
humanize.SI(0.00000000223, "M") // 2.23 nM
|
||||
```
|
||||
|
||||
## English-specific functions
|
||||
|
||||
The following functions are in the `humanize/english` subpackage.
|
||||
|
||||
### Plurals
|
||||
|
||||
Simple English pluralization
|
||||
|
||||
```go
|
||||
english.PluralWord(1, "object", "") // object
|
||||
english.PluralWord(42, "object", "") // objects
|
||||
english.PluralWord(2, "bus", "") // buses
|
||||
english.PluralWord(99, "locus", "loci") // loci
|
||||
|
||||
english.Plural(1, "object", "") // 1 object
|
||||
english.Plural(42, "object", "") // 42 objects
|
||||
english.Plural(2, "bus", "") // 2 buses
|
||||
english.Plural(99, "locus", "loci") // 99 loci
|
||||
```
|
||||
|
||||
### Word series
|
||||
|
||||
Format comma-separated words lists with conjuctions:
|
||||
|
||||
```go
|
||||
english.WordSeries([]string{"foo"}, "and") // foo
|
||||
english.WordSeries([]string{"foo", "bar"}, "and") // foo and bar
|
||||
english.WordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar and baz
|
||||
|
||||
english.OxfordWordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar, and baz
|
||||
```
|
||||
|
||||
[odisc]: https://groups.google.com/d/topic/golang-nuts/l8NhI74jl-4/discussion
|
||||
[sinotation]: http://en.wikipedia.org/wiki/Metric_prefix
|
||||
31
vendor/github.com/dustin/go-humanize/big.go
generated
vendored
Normal file
31
vendor/github.com/dustin/go-humanize/big.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
package humanize
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// order of magnitude (to a max order)
|
||||
func oomm(n, b *big.Int, maxmag int) (float64, int) {
|
||||
mag := 0
|
||||
m := &big.Int{}
|
||||
for n.Cmp(b) >= 0 {
|
||||
n.DivMod(n, b, m)
|
||||
mag++
|
||||
if mag == maxmag && maxmag >= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
|
||||
}
|
||||
|
||||
// total order of magnitude
|
||||
// (same as above, but with no upper limit)
|
||||
func oom(n, b *big.Int) (float64, int) {
|
||||
mag := 0
|
||||
m := &big.Int{}
|
||||
for n.Cmp(b) >= 0 {
|
||||
n.DivMod(n, b, m)
|
||||
mag++
|
||||
}
|
||||
return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
|
||||
}
|
||||
189
vendor/github.com/dustin/go-humanize/bigbytes.go
generated
vendored
Normal file
189
vendor/github.com/dustin/go-humanize/bigbytes.go
generated
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
package humanize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
bigIECExp = big.NewInt(1024)
|
||||
|
||||
// BigByte is one byte in bit.Ints
|
||||
BigByte = big.NewInt(1)
|
||||
// BigKiByte is 1,024 bytes in bit.Ints
|
||||
BigKiByte = (&big.Int{}).Mul(BigByte, bigIECExp)
|
||||
// BigMiByte is 1,024 k bytes in bit.Ints
|
||||
BigMiByte = (&big.Int{}).Mul(BigKiByte, bigIECExp)
|
||||
// BigGiByte is 1,024 m bytes in bit.Ints
|
||||
BigGiByte = (&big.Int{}).Mul(BigMiByte, bigIECExp)
|
||||
// BigTiByte is 1,024 g bytes in bit.Ints
|
||||
BigTiByte = (&big.Int{}).Mul(BigGiByte, bigIECExp)
|
||||
// BigPiByte is 1,024 t bytes in bit.Ints
|
||||
BigPiByte = (&big.Int{}).Mul(BigTiByte, bigIECExp)
|
||||
// BigEiByte is 1,024 p bytes in bit.Ints
|
||||
BigEiByte = (&big.Int{}).Mul(BigPiByte, bigIECExp)
|
||||
// BigZiByte is 1,024 e bytes in bit.Ints
|
||||
BigZiByte = (&big.Int{}).Mul(BigEiByte, bigIECExp)
|
||||
// BigYiByte is 1,024 z bytes in bit.Ints
|
||||
BigYiByte = (&big.Int{}).Mul(BigZiByte, bigIECExp)
|
||||
// BigRiByte is 1,024 y bytes in bit.Ints
|
||||
BigRiByte = (&big.Int{}).Mul(BigYiByte, bigIECExp)
|
||||
// BigQiByte is 1,024 r bytes in bit.Ints
|
||||
BigQiByte = (&big.Int{}).Mul(BigRiByte, bigIECExp)
|
||||
)
|
||||
|
||||
var (
|
||||
bigSIExp = big.NewInt(1000)
|
||||
|
||||
// BigSIByte is one SI byte in big.Ints
|
||||
BigSIByte = big.NewInt(1)
|
||||
// BigKByte is 1,000 SI bytes in big.Ints
|
||||
BigKByte = (&big.Int{}).Mul(BigSIByte, bigSIExp)
|
||||
// BigMByte is 1,000 SI k bytes in big.Ints
|
||||
BigMByte = (&big.Int{}).Mul(BigKByte, bigSIExp)
|
||||
// BigGByte is 1,000 SI m bytes in big.Ints
|
||||
BigGByte = (&big.Int{}).Mul(BigMByte, bigSIExp)
|
||||
// BigTByte is 1,000 SI g bytes in big.Ints
|
||||
BigTByte = (&big.Int{}).Mul(BigGByte, bigSIExp)
|
||||
// BigPByte is 1,000 SI t bytes in big.Ints
|
||||
BigPByte = (&big.Int{}).Mul(BigTByte, bigSIExp)
|
||||
// BigEByte is 1,000 SI p bytes in big.Ints
|
||||
BigEByte = (&big.Int{}).Mul(BigPByte, bigSIExp)
|
||||
// BigZByte is 1,000 SI e bytes in big.Ints
|
||||
BigZByte = (&big.Int{}).Mul(BigEByte, bigSIExp)
|
||||
// BigYByte is 1,000 SI z bytes in big.Ints
|
||||
BigYByte = (&big.Int{}).Mul(BigZByte, bigSIExp)
|
||||
// BigRByte is 1,000 SI y bytes in big.Ints
|
||||
BigRByte = (&big.Int{}).Mul(BigYByte, bigSIExp)
|
||||
// BigQByte is 1,000 SI r bytes in big.Ints
|
||||
BigQByte = (&big.Int{}).Mul(BigRByte, bigSIExp)
|
||||
)
|
||||
|
||||
var bigBytesSizeTable = map[string]*big.Int{
|
||||
"b": BigByte,
|
||||
"kib": BigKiByte,
|
||||
"kb": BigKByte,
|
||||
"mib": BigMiByte,
|
||||
"mb": BigMByte,
|
||||
"gib": BigGiByte,
|
||||
"gb": BigGByte,
|
||||
"tib": BigTiByte,
|
||||
"tb": BigTByte,
|
||||
"pib": BigPiByte,
|
||||
"pb": BigPByte,
|
||||
"eib": BigEiByte,
|
||||
"eb": BigEByte,
|
||||
"zib": BigZiByte,
|
||||
"zb": BigZByte,
|
||||
"yib": BigYiByte,
|
||||
"yb": BigYByte,
|
||||
"rib": BigRiByte,
|
||||
"rb": BigRByte,
|
||||
"qib": BigQiByte,
|
||||
"qb": BigQByte,
|
||||
// Without suffix
|
||||
"": BigByte,
|
||||
"ki": BigKiByte,
|
||||
"k": BigKByte,
|
||||
"mi": BigMiByte,
|
||||
"m": BigMByte,
|
||||
"gi": BigGiByte,
|
||||
"g": BigGByte,
|
||||
"ti": BigTiByte,
|
||||
"t": BigTByte,
|
||||
"pi": BigPiByte,
|
||||
"p": BigPByte,
|
||||
"ei": BigEiByte,
|
||||
"e": BigEByte,
|
||||
"z": BigZByte,
|
||||
"zi": BigZiByte,
|
||||
"y": BigYByte,
|
||||
"yi": BigYiByte,
|
||||
"r": BigRByte,
|
||||
"ri": BigRiByte,
|
||||
"q": BigQByte,
|
||||
"qi": BigQiByte,
|
||||
}
|
||||
|
||||
var ten = big.NewInt(10)
|
||||
|
||||
func humanateBigBytes(s, base *big.Int, sizes []string) string {
|
||||
if s.Cmp(ten) < 0 {
|
||||
return fmt.Sprintf("%d B", s)
|
||||
}
|
||||
c := (&big.Int{}).Set(s)
|
||||
val, mag := oomm(c, base, len(sizes)-1)
|
||||
suffix := sizes[mag]
|
||||
f := "%.0f %s"
|
||||
if val < 10 {
|
||||
f = "%.1f %s"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(f, val, suffix)
|
||||
|
||||
}
|
||||
|
||||
// BigBytes produces a human readable representation of an SI size.
|
||||
//
|
||||
// See also: ParseBigBytes.
|
||||
//
|
||||
// BigBytes(82854982) -> 83 MB
|
||||
func BigBytes(s *big.Int) string {
|
||||
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "RB", "QB"}
|
||||
return humanateBigBytes(s, bigSIExp, sizes)
|
||||
}
|
||||
|
||||
// BigIBytes produces a human readable representation of an IEC size.
|
||||
//
|
||||
// See also: ParseBigBytes.
|
||||
//
|
||||
// BigIBytes(82854982) -> 79 MiB
|
||||
func BigIBytes(s *big.Int) string {
|
||||
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB"}
|
||||
return humanateBigBytes(s, bigIECExp, sizes)
|
||||
}
|
||||
|
||||
// ParseBigBytes parses a string representation of bytes into the number
|
||||
// of bytes it represents.
|
||||
//
|
||||
// See also: BigBytes, BigIBytes.
|
||||
//
|
||||
// ParseBigBytes("42 MB") -> 42000000, nil
|
||||
// ParseBigBytes("42 mib") -> 44040192, nil
|
||||
func ParseBigBytes(s string) (*big.Int, error) {
|
||||
lastDigit := 0
|
||||
hasComma := false
|
||||
for _, r := range s {
|
||||
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
|
||||
break
|
||||
}
|
||||
if r == ',' {
|
||||
hasComma = true
|
||||
}
|
||||
lastDigit++
|
||||
}
|
||||
|
||||
num := s[:lastDigit]
|
||||
if hasComma {
|
||||
num = strings.Replace(num, ",", "", -1)
|
||||
}
|
||||
|
||||
val := &big.Rat{}
|
||||
_, err := fmt.Sscanf(num, "%f", val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
|
||||
if m, ok := bigBytesSizeTable[extra]; ok {
|
||||
mv := (&big.Rat{}).SetInt(m)
|
||||
val.Mul(val, mv)
|
||||
rv := &big.Int{}
|
||||
rv.Div(val.Num(), val.Denom())
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unhandled size name: %v", extra)
|
||||
}
|
||||
143
vendor/github.com/dustin/go-humanize/bytes.go
generated
vendored
Normal file
143
vendor/github.com/dustin/go-humanize/bytes.go
generated
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
package humanize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// IEC Sizes.
|
||||
// kibis of bits
|
||||
const (
|
||||
Byte = 1 << (iota * 10)
|
||||
KiByte
|
||||
MiByte
|
||||
GiByte
|
||||
TiByte
|
||||
PiByte
|
||||
EiByte
|
||||
)
|
||||
|
||||
// SI Sizes.
|
||||
const (
|
||||
IByte = 1
|
||||
KByte = IByte * 1000
|
||||
MByte = KByte * 1000
|
||||
GByte = MByte * 1000
|
||||
TByte = GByte * 1000
|
||||
PByte = TByte * 1000
|
||||
EByte = PByte * 1000
|
||||
)
|
||||
|
||||
var bytesSizeTable = map[string]uint64{
|
||||
"b": Byte,
|
||||
"kib": KiByte,
|
||||
"kb": KByte,
|
||||
"mib": MiByte,
|
||||
"mb": MByte,
|
||||
"gib": GiByte,
|
||||
"gb": GByte,
|
||||
"tib": TiByte,
|
||||
"tb": TByte,
|
||||
"pib": PiByte,
|
||||
"pb": PByte,
|
||||
"eib": EiByte,
|
||||
"eb": EByte,
|
||||
// Without suffix
|
||||
"": Byte,
|
||||
"ki": KiByte,
|
||||
"k": KByte,
|
||||
"mi": MiByte,
|
||||
"m": MByte,
|
||||
"gi": GiByte,
|
||||
"g": GByte,
|
||||
"ti": TiByte,
|
||||
"t": TByte,
|
||||
"pi": PiByte,
|
||||
"p": PByte,
|
||||
"ei": EiByte,
|
||||
"e": EByte,
|
||||
}
|
||||
|
||||
func logn(n, b float64) float64 {
|
||||
return math.Log(n) / math.Log(b)
|
||||
}
|
||||
|
||||
func humanateBytes(s uint64, base float64, sizes []string) string {
|
||||
if s < 10 {
|
||||
return fmt.Sprintf("%d B", s)
|
||||
}
|
||||
e := math.Floor(logn(float64(s), base))
|
||||
suffix := sizes[int(e)]
|
||||
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
|
||||
f := "%.0f %s"
|
||||
if val < 10 {
|
||||
f = "%.1f %s"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(f, val, suffix)
|
||||
}
|
||||
|
||||
// Bytes produces a human readable representation of an SI size.
|
||||
//
|
||||
// See also: ParseBytes.
|
||||
//
|
||||
// Bytes(82854982) -> 83 MB
|
||||
func Bytes(s uint64) string {
|
||||
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
|
||||
return humanateBytes(s, 1000, sizes)
|
||||
}
|
||||
|
||||
// IBytes produces a human readable representation of an IEC size.
|
||||
//
|
||||
// See also: ParseBytes.
|
||||
//
|
||||
// IBytes(82854982) -> 79 MiB
|
||||
func IBytes(s uint64) string {
|
||||
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
|
||||
return humanateBytes(s, 1024, sizes)
|
||||
}
|
||||
|
||||
// ParseBytes parses a string representation of bytes into the number
|
||||
// of bytes it represents.
|
||||
//
|
||||
// See Also: Bytes, IBytes.
|
||||
//
|
||||
// ParseBytes("42 MB") -> 42000000, nil
|
||||
// ParseBytes("42 mib") -> 44040192, nil
|
||||
func ParseBytes(s string) (uint64, error) {
|
||||
lastDigit := 0
|
||||
hasComma := false
|
||||
for _, r := range s {
|
||||
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
|
||||
break
|
||||
}
|
||||
if r == ',' {
|
||||
hasComma = true
|
||||
}
|
||||
lastDigit++
|
||||
}
|
||||
|
||||
num := s[:lastDigit]
|
||||
if hasComma {
|
||||
num = strings.Replace(num, ",", "", -1)
|
||||
}
|
||||
|
||||
f, err := strconv.ParseFloat(num, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
|
||||
if m, ok := bytesSizeTable[extra]; ok {
|
||||
f *= float64(m)
|
||||
if f >= math.MaxUint64 {
|
||||
return 0, fmt.Errorf("too large: %v", s)
|
||||
}
|
||||
return uint64(f), nil
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("unhandled size name: %v", extra)
|
||||
}
|
||||
116
vendor/github.com/dustin/go-humanize/comma.go
generated
vendored
Normal file
116
vendor/github.com/dustin/go-humanize/comma.go
generated
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
package humanize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Comma produces a string form of the given number in base 10 with
|
||||
// commas after every three orders of magnitude.
|
||||
//
|
||||
// e.g. Comma(834142) -> 834,142
|
||||
func Comma(v int64) string {
|
||||
sign := ""
|
||||
|
||||
// Min int64 can't be negated to a usable value, so it has to be special cased.
|
||||
if v == math.MinInt64 {
|
||||
return "-9,223,372,036,854,775,808"
|
||||
}
|
||||
|
||||
if v < 0 {
|
||||
sign = "-"
|
||||
v = 0 - v
|
||||
}
|
||||
|
||||
parts := []string{"", "", "", "", "", "", ""}
|
||||
j := len(parts) - 1
|
||||
|
||||
for v > 999 {
|
||||
parts[j] = strconv.FormatInt(v%1000, 10)
|
||||
switch len(parts[j]) {
|
||||
case 2:
|
||||
parts[j] = "0" + parts[j]
|
||||
case 1:
|
||||
parts[j] = "00" + parts[j]
|
||||
}
|
||||
v = v / 1000
|
||||
j--
|
||||
}
|
||||
parts[j] = strconv.Itoa(int(v))
|
||||
return sign + strings.Join(parts[j:], ",")
|
||||
}
|
||||
|
||||
// Commaf produces a string form of the given number in base 10 with
|
||||
// commas after every three orders of magnitude.
|
||||
//
|
||||
// e.g. Commaf(834142.32) -> 834,142.32
|
||||
func Commaf(v float64) string {
|
||||
buf := &bytes.Buffer{}
|
||||
if v < 0 {
|
||||
buf.Write([]byte{'-'})
|
||||
v = 0 - v
|
||||
}
|
||||
|
||||
comma := []byte{','}
|
||||
|
||||
parts := strings.Split(strconv.FormatFloat(v, 'f', -1, 64), ".")
|
||||
pos := 0
|
||||
if len(parts[0])%3 != 0 {
|
||||
pos += len(parts[0]) % 3
|
||||
buf.WriteString(parts[0][:pos])
|
||||
buf.Write(comma)
|
||||
}
|
||||
for ; pos < len(parts[0]); pos += 3 {
|
||||
buf.WriteString(parts[0][pos : pos+3])
|
||||
buf.Write(comma)
|
||||
}
|
||||
buf.Truncate(buf.Len() - 1)
|
||||
|
||||
if len(parts) > 1 {
|
||||
buf.Write([]byte{'.'})
|
||||
buf.WriteString(parts[1])
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// CommafWithDigits works like the Commaf but limits the resulting
|
||||
// string to the given number of decimal places.
|
||||
//
|
||||
// e.g. CommafWithDigits(834142.32, 1) -> 834,142.3
|
||||
func CommafWithDigits(f float64, decimals int) string {
|
||||
return stripTrailingDigits(Commaf(f), decimals)
|
||||
}
|
||||
|
||||
// BigComma produces a string form of the given big.Int in base 10
|
||||
// with commas after every three orders of magnitude.
|
||||
func BigComma(b *big.Int) string {
|
||||
sign := ""
|
||||
if b.Sign() < 0 {
|
||||
sign = "-"
|
||||
b.Abs(b)
|
||||
}
|
||||
|
||||
athousand := big.NewInt(1000)
|
||||
c := (&big.Int{}).Set(b)
|
||||
_, m := oom(c, athousand)
|
||||
parts := make([]string, m+1)
|
||||
j := len(parts) - 1
|
||||
|
||||
mod := &big.Int{}
|
||||
for b.Cmp(athousand) >= 0 {
|
||||
b.DivMod(b, athousand, mod)
|
||||
parts[j] = strconv.FormatInt(mod.Int64(), 10)
|
||||
switch len(parts[j]) {
|
||||
case 2:
|
||||
parts[j] = "0" + parts[j]
|
||||
case 1:
|
||||
parts[j] = "00" + parts[j]
|
||||
}
|
||||
j--
|
||||
}
|
||||
parts[j] = strconv.Itoa(int(b.Int64()))
|
||||
return sign + strings.Join(parts[j:], ",")
|
||||
}
|
||||
41
vendor/github.com/dustin/go-humanize/commaf.go
generated
vendored
Normal file
41
vendor/github.com/dustin/go-humanize/commaf.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
//go:build go1.6
|
||||
// +build go1.6
|
||||
|
||||
package humanize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BigCommaf produces a string form of the given big.Float in base 10
|
||||
// with commas after every three orders of magnitude.
|
||||
func BigCommaf(v *big.Float) string {
|
||||
buf := &bytes.Buffer{}
|
||||
if v.Sign() < 0 {
|
||||
buf.Write([]byte{'-'})
|
||||
v.Abs(v)
|
||||
}
|
||||
|
||||
comma := []byte{','}
|
||||
|
||||
parts := strings.Split(v.Text('f', -1), ".")
|
||||
pos := 0
|
||||
if len(parts[0])%3 != 0 {
|
||||
pos += len(parts[0]) % 3
|
||||
buf.WriteString(parts[0][:pos])
|
||||
buf.Write(comma)
|
||||
}
|
||||
for ; pos < len(parts[0]); pos += 3 {
|
||||
buf.WriteString(parts[0][pos : pos+3])
|
||||
buf.Write(comma)
|
||||
}
|
||||
buf.Truncate(buf.Len() - 1)
|
||||
|
||||
if len(parts) > 1 {
|
||||
buf.Write([]byte{'.'})
|
||||
buf.WriteString(parts[1])
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
49
vendor/github.com/dustin/go-humanize/ftoa.go
generated
vendored
Normal file
49
vendor/github.com/dustin/go-humanize/ftoa.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
package humanize
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func stripTrailingZeros(s string) string {
|
||||
if !strings.ContainsRune(s, '.') {
|
||||
return s
|
||||
}
|
||||
offset := len(s) - 1
|
||||
for offset > 0 {
|
||||
if s[offset] == '.' {
|
||||
offset--
|
||||
break
|
||||
}
|
||||
if s[offset] != '0' {
|
||||
break
|
||||
}
|
||||
offset--
|
||||
}
|
||||
return s[:offset+1]
|
||||
}
|
||||
|
||||
func stripTrailingDigits(s string, digits int) string {
|
||||
if i := strings.Index(s, "."); i >= 0 {
|
||||
if digits <= 0 {
|
||||
return s[:i]
|
||||
}
|
||||
i++
|
||||
if i+digits >= len(s) {
|
||||
return s
|
||||
}
|
||||
return s[:i+digits]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Ftoa converts a float to a string with no trailing zeros.
|
||||
func Ftoa(num float64) string {
|
||||
return stripTrailingZeros(strconv.FormatFloat(num, 'f', 6, 64))
|
||||
}
|
||||
|
||||
// FtoaWithDigits converts a float to a string but limits the resulting string
|
||||
// to the given number of decimal places, and no trailing zeros.
|
||||
func FtoaWithDigits(num float64, digits int) string {
|
||||
return stripTrailingZeros(stripTrailingDigits(strconv.FormatFloat(num, 'f', 6, 64), digits))
|
||||
}
|
||||
8
vendor/github.com/dustin/go-humanize/humanize.go
generated
vendored
Normal file
8
vendor/github.com/dustin/go-humanize/humanize.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
Package humanize converts boring ugly numbers to human-friendly strings and back.
|
||||
|
||||
Durations can be turned into strings such as "3 days ago", numbers
|
||||
representing sizes like 82854982 into useful strings like, "83 MB" or
|
||||
"79 MiB" (whichever you prefer).
|
||||
*/
|
||||
package humanize
|
||||
192
vendor/github.com/dustin/go-humanize/number.go
generated
vendored
Normal file
192
vendor/github.com/dustin/go-humanize/number.go
generated
vendored
Normal file
@@ -0,0 +1,192 @@
|
||||
package humanize
|
||||
|
||||
/*
|
||||
Slightly adapted from the source to fit go-humanize.
|
||||
|
||||
Author: https://github.com/gorhill
|
||||
Source: https://gist.github.com/gorhill/5285193
|
||||
|
||||
*/
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
renderFloatPrecisionMultipliers = [...]float64{
|
||||
1,
|
||||
10,
|
||||
100,
|
||||
1000,
|
||||
10000,
|
||||
100000,
|
||||
1000000,
|
||||
10000000,
|
||||
100000000,
|
||||
1000000000,
|
||||
}
|
||||
|
||||
renderFloatPrecisionRounders = [...]float64{
|
||||
0.5,
|
||||
0.05,
|
||||
0.005,
|
||||
0.0005,
|
||||
0.00005,
|
||||
0.000005,
|
||||
0.0000005,
|
||||
0.00000005,
|
||||
0.000000005,
|
||||
0.0000000005,
|
||||
}
|
||||
)
|
||||
|
||||
// FormatFloat produces a formatted number as string based on the following user-specified criteria:
|
||||
// * thousands separator
|
||||
// * decimal separator
|
||||
// * decimal precision
|
||||
//
|
||||
// Usage: s := RenderFloat(format, n)
|
||||
// The format parameter tells how to render the number n.
|
||||
//
|
||||
// See examples: http://play.golang.org/p/LXc1Ddm1lJ
|
||||
//
|
||||
// Examples of format strings, given n = 12345.6789:
|
||||
// "#,###.##" => "12,345.67"
|
||||
// "#,###." => "12,345"
|
||||
// "#,###" => "12345,678"
|
||||
// "#\u202F###,##" => "12 345,68"
|
||||
// "#.###,###### => 12.345,678900
|
||||
// "" (aka default format) => 12,345.67
|
||||
//
|
||||
// The highest precision allowed is 9 digits after the decimal symbol.
|
||||
// There is also a version for integer number, FormatInteger(),
|
||||
// which is convenient for calls within template.
|
||||
func FormatFloat(format string, n float64) string {
|
||||
// Special cases:
|
||||
// NaN = "NaN"
|
||||
// +Inf = "+Infinity"
|
||||
// -Inf = "-Infinity"
|
||||
if math.IsNaN(n) {
|
||||
return "NaN"
|
||||
}
|
||||
if n > math.MaxFloat64 {
|
||||
return "Infinity"
|
||||
}
|
||||
if n < (0.0 - math.MaxFloat64) {
|
||||
return "-Infinity"
|
||||
}
|
||||
|
||||
// default format
|
||||
precision := 2
|
||||
decimalStr := "."
|
||||
thousandStr := ","
|
||||
positiveStr := ""
|
||||
negativeStr := "-"
|
||||
|
||||
if len(format) > 0 {
|
||||
format := []rune(format)
|
||||
|
||||
// If there is an explicit format directive,
|
||||
// then default values are these:
|
||||
precision = 9
|
||||
thousandStr = ""
|
||||
|
||||
// collect indices of meaningful formatting directives
|
||||
formatIndx := []int{}
|
||||
for i, char := range format {
|
||||
if char != '#' && char != '0' {
|
||||
formatIndx = append(formatIndx, i)
|
||||
}
|
||||
}
|
||||
|
||||
if len(formatIndx) > 0 {
|
||||
// Directive at index 0:
|
||||
// Must be a '+'
|
||||
// Raise an error if not the case
|
||||
// index: 0123456789
|
||||
// +0.000,000
|
||||
// +000,000.0
|
||||
// +0000.00
|
||||
// +0000
|
||||
if formatIndx[0] == 0 {
|
||||
if format[formatIndx[0]] != '+' {
|
||||
panic("RenderFloat(): invalid positive sign directive")
|
||||
}
|
||||
positiveStr = "+"
|
||||
formatIndx = formatIndx[1:]
|
||||
}
|
||||
|
||||
// Two directives:
|
||||
// First is thousands separator
|
||||
// Raise an error if not followed by 3-digit
|
||||
// 0123456789
|
||||
// 0.000,000
|
||||
// 000,000.00
|
||||
if len(formatIndx) == 2 {
|
||||
if (formatIndx[1] - formatIndx[0]) != 4 {
|
||||
panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers")
|
||||
}
|
||||
thousandStr = string(format[formatIndx[0]])
|
||||
formatIndx = formatIndx[1:]
|
||||
}
|
||||
|
||||
// One directive:
|
||||
// Directive is decimal separator
|
||||
// The number of digit-specifier following the separator indicates wanted precision
|
||||
// 0123456789
|
||||
// 0.00
|
||||
// 000,0000
|
||||
if len(formatIndx) == 1 {
|
||||
decimalStr = string(format[formatIndx[0]])
|
||||
precision = len(format) - formatIndx[0] - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generate sign part
|
||||
var signStr string
|
||||
if n >= 0.000000001 {
|
||||
signStr = positiveStr
|
||||
} else if n <= -0.000000001 {
|
||||
signStr = negativeStr
|
||||
n = -n
|
||||
} else {
|
||||
signStr = ""
|
||||
n = 0.0
|
||||
}
|
||||
|
||||
// split number into integer and fractional parts
|
||||
intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision])
|
||||
|
||||
// generate integer part string
|
||||
intStr := strconv.FormatInt(int64(intf), 10)
|
||||
|
||||
// add thousand separator if required
|
||||
if len(thousandStr) > 0 {
|
||||
for i := len(intStr); i > 3; {
|
||||
i -= 3
|
||||
intStr = intStr[:i] + thousandStr + intStr[i:]
|
||||
}
|
||||
}
|
||||
|
||||
// no fractional part, we can leave now
|
||||
if precision == 0 {
|
||||
return signStr + intStr
|
||||
}
|
||||
|
||||
// generate fractional part
|
||||
fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision]))
|
||||
// may need padding
|
||||
if len(fracStr) < precision {
|
||||
fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr
|
||||
}
|
||||
|
||||
return signStr + intStr + decimalStr + fracStr
|
||||
}
|
||||
|
||||
// FormatInteger produces a formatted number as string.
|
||||
// See FormatFloat.
|
||||
func FormatInteger(format string, n int) string {
|
||||
return FormatFloat(format, float64(n))
|
||||
}
|
||||
25
vendor/github.com/dustin/go-humanize/ordinals.go
generated
vendored
Normal file
25
vendor/github.com/dustin/go-humanize/ordinals.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
package humanize
|
||||
|
||||
import "strconv"
|
||||
|
||||
// Ordinal gives you the input number in a rank/ordinal format.
|
||||
//
|
||||
// Ordinal(3) -> 3rd
|
||||
func Ordinal(x int) string {
|
||||
suffix := "th"
|
||||
switch x % 10 {
|
||||
case 1:
|
||||
if x%100 != 11 {
|
||||
suffix = "st"
|
||||
}
|
||||
case 2:
|
||||
if x%100 != 12 {
|
||||
suffix = "nd"
|
||||
}
|
||||
case 3:
|
||||
if x%100 != 13 {
|
||||
suffix = "rd"
|
||||
}
|
||||
}
|
||||
return strconv.Itoa(x) + suffix
|
||||
}
|
||||
127
vendor/github.com/dustin/go-humanize/si.go
generated
vendored
Normal file
127
vendor/github.com/dustin/go-humanize/si.go
generated
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
package humanize
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var siPrefixTable = map[float64]string{
|
||||
-30: "q", // quecto
|
||||
-27: "r", // ronto
|
||||
-24: "y", // yocto
|
||||
-21: "z", // zepto
|
||||
-18: "a", // atto
|
||||
-15: "f", // femto
|
||||
-12: "p", // pico
|
||||
-9: "n", // nano
|
||||
-6: "µ", // micro
|
||||
-3: "m", // milli
|
||||
0: "",
|
||||
3: "k", // kilo
|
||||
6: "M", // mega
|
||||
9: "G", // giga
|
||||
12: "T", // tera
|
||||
15: "P", // peta
|
||||
18: "E", // exa
|
||||
21: "Z", // zetta
|
||||
24: "Y", // yotta
|
||||
27: "R", // ronna
|
||||
30: "Q", // quetta
|
||||
}
|
||||
|
||||
var revSIPrefixTable = revfmap(siPrefixTable)
|
||||
|
||||
// revfmap reverses the map and precomputes the power multiplier
|
||||
func revfmap(in map[float64]string) map[string]float64 {
|
||||
rv := map[string]float64{}
|
||||
for k, v := range in {
|
||||
rv[v] = math.Pow(10, k)
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
var riParseRegex *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
ri := `^([\-0-9.]+)\s?([`
|
||||
for _, v := range siPrefixTable {
|
||||
ri += v
|
||||
}
|
||||
ri += `]?)(.*)`
|
||||
|
||||
riParseRegex = regexp.MustCompile(ri)
|
||||
}
|
||||
|
||||
// ComputeSI finds the most appropriate SI prefix for the given number
|
||||
// and returns the prefix along with the value adjusted to be within
|
||||
// that prefix.
|
||||
//
|
||||
// See also: SI, ParseSI.
|
||||
//
|
||||
// e.g. ComputeSI(2.2345e-12) -> (2.2345, "p")
|
||||
func ComputeSI(input float64) (float64, string) {
|
||||
if input == 0 {
|
||||
return 0, ""
|
||||
}
|
||||
mag := math.Abs(input)
|
||||
exponent := math.Floor(logn(mag, 10))
|
||||
exponent = math.Floor(exponent/3) * 3
|
||||
|
||||
value := mag / math.Pow(10, exponent)
|
||||
|
||||
// Handle special case where value is exactly 1000.0
|
||||
// Should return 1 M instead of 1000 k
|
||||
if value == 1000.0 {
|
||||
exponent += 3
|
||||
value = mag / math.Pow(10, exponent)
|
||||
}
|
||||
|
||||
value = math.Copysign(value, input)
|
||||
|
||||
prefix := siPrefixTable[exponent]
|
||||
return value, prefix
|
||||
}
|
||||
|
||||
// SI returns a string with default formatting.
|
||||
//
|
||||
// SI uses Ftoa to format float value, removing trailing zeros.
|
||||
//
|
||||
// See also: ComputeSI, ParseSI.
|
||||
//
|
||||
// e.g. SI(1000000, "B") -> 1 MB
|
||||
// e.g. SI(2.2345e-12, "F") -> 2.2345 pF
|
||||
func SI(input float64, unit string) string {
|
||||
value, prefix := ComputeSI(input)
|
||||
return Ftoa(value) + " " + prefix + unit
|
||||
}
|
||||
|
||||
// SIWithDigits works like SI but limits the resulting string to the
|
||||
// given number of decimal places.
|
||||
//
|
||||
// e.g. SIWithDigits(1000000, 0, "B") -> 1 MB
|
||||
// e.g. SIWithDigits(2.2345e-12, 2, "F") -> 2.23 pF
|
||||
func SIWithDigits(input float64, decimals int, unit string) string {
|
||||
value, prefix := ComputeSI(input)
|
||||
return FtoaWithDigits(value, decimals) + " " + prefix + unit
|
||||
}
|
||||
|
||||
var errInvalid = errors.New("invalid input")
|
||||
|
||||
// ParseSI parses an SI string back into the number and unit.
|
||||
//
|
||||
// See also: SI, ComputeSI.
|
||||
//
|
||||
// e.g. ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil)
|
||||
func ParseSI(input string) (float64, string, error) {
|
||||
found := riParseRegex.FindStringSubmatch(input)
|
||||
if len(found) != 4 {
|
||||
return 0, "", errInvalid
|
||||
}
|
||||
mag := revSIPrefixTable[found[2]]
|
||||
unit := found[3]
|
||||
|
||||
base, err := strconv.ParseFloat(found[1], 64)
|
||||
return base * mag, unit, err
|
||||
}
|
||||
117
vendor/github.com/dustin/go-humanize/times.go
generated
vendored
Normal file
117
vendor/github.com/dustin/go-humanize/times.go
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
package humanize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Seconds-based time units
|
||||
const (
|
||||
Day = 24 * time.Hour
|
||||
Week = 7 * Day
|
||||
Month = 30 * Day
|
||||
Year = 12 * Month
|
||||
LongTime = 37 * Year
|
||||
)
|
||||
|
||||
// Time formats a time into a relative string.
|
||||
//
|
||||
// Time(someT) -> "3 weeks ago"
|
||||
func Time(then time.Time) string {
|
||||
return RelTime(then, time.Now(), "ago", "from now")
|
||||
}
|
||||
|
||||
// A RelTimeMagnitude struct contains a relative time point at which
|
||||
// the relative format of time will switch to a new format string. A
|
||||
// slice of these in ascending order by their "D" field is passed to
|
||||
// CustomRelTime to format durations.
|
||||
//
|
||||
// The Format field is a string that may contain a "%s" which will be
|
||||
// replaced with the appropriate signed label (e.g. "ago" or "from
|
||||
// now") and a "%d" that will be replaced by the quantity.
|
||||
//
|
||||
// The DivBy field is the amount of time the time difference must be
|
||||
// divided by in order to display correctly.
|
||||
//
|
||||
// e.g. if D is 2*time.Minute and you want to display "%d minutes %s"
|
||||
// DivBy should be time.Minute so whatever the duration is will be
|
||||
// expressed in minutes.
|
||||
type RelTimeMagnitude struct {
|
||||
D time.Duration
|
||||
Format string
|
||||
DivBy time.Duration
|
||||
}
|
||||
|
||||
var defaultMagnitudes = []RelTimeMagnitude{
|
||||
{time.Second, "now", time.Second},
|
||||
{2 * time.Second, "1 second %s", 1},
|
||||
{time.Minute, "%d seconds %s", time.Second},
|
||||
{2 * time.Minute, "1 minute %s", 1},
|
||||
{time.Hour, "%d minutes %s", time.Minute},
|
||||
{2 * time.Hour, "1 hour %s", 1},
|
||||
{Day, "%d hours %s", time.Hour},
|
||||
{2 * Day, "1 day %s", 1},
|
||||
{Week, "%d days %s", Day},
|
||||
{2 * Week, "1 week %s", 1},
|
||||
{Month, "%d weeks %s", Week},
|
||||
{2 * Month, "1 month %s", 1},
|
||||
{Year, "%d months %s", Month},
|
||||
{18 * Month, "1 year %s", 1},
|
||||
{2 * Year, "2 years %s", 1},
|
||||
{LongTime, "%d years %s", Year},
|
||||
{math.MaxInt64, "a long while %s", 1},
|
||||
}
|
||||
|
||||
// RelTime formats a time into a relative string.
|
||||
//
|
||||
// It takes two times and two labels. In addition to the generic time
|
||||
// delta string (e.g. 5 minutes), the labels are used applied so that
|
||||
// the label corresponding to the smaller time is applied.
|
||||
//
|
||||
// RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier"
|
||||
func RelTime(a, b time.Time, albl, blbl string) string {
|
||||
return CustomRelTime(a, b, albl, blbl, defaultMagnitudes)
|
||||
}
|
||||
|
||||
// CustomRelTime formats a time into a relative string.
|
||||
//
|
||||
// It takes two times two labels and a table of relative time formats.
|
||||
// In addition to the generic time delta string (e.g. 5 minutes), the
|
||||
// labels are used applied so that the label corresponding to the
|
||||
// smaller time is applied.
|
||||
func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnitude) string {
|
||||
lbl := albl
|
||||
diff := b.Sub(a)
|
||||
|
||||
if a.After(b) {
|
||||
lbl = blbl
|
||||
diff = a.Sub(b)
|
||||
}
|
||||
|
||||
n := sort.Search(len(magnitudes), func(i int) bool {
|
||||
return magnitudes[i].D > diff
|
||||
})
|
||||
|
||||
if n >= len(magnitudes) {
|
||||
n = len(magnitudes) - 1
|
||||
}
|
||||
mag := magnitudes[n]
|
||||
args := []interface{}{}
|
||||
escaped := false
|
||||
for _, ch := range mag.Format {
|
||||
if escaped {
|
||||
switch ch {
|
||||
case 's':
|
||||
args = append(args, lbl)
|
||||
case 'd':
|
||||
args = append(args, diff/mag.DivBy)
|
||||
}
|
||||
escaped = false
|
||||
} else {
|
||||
escaped = ch == '%'
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf(mag.Format, args...)
|
||||
}
|
||||
1
vendor/github.com/go-chi/chi/v5/path_value.go
generated
vendored
1
vendor/github.com/go-chi/chi/v5/path_value.go
generated
vendored
@@ -1,6 +1,7 @@
|
||||
//go:build go1.22 && !tinygo
|
||||
// +build go1.22,!tinygo
|
||||
|
||||
|
||||
package chi
|
||||
|
||||
import "net/http"
|
||||
|
||||
41
vendor/github.com/google/uuid/CHANGELOG.md
generated
vendored
Normal file
41
vendor/github.com/google/uuid/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
# Changelog
|
||||
|
||||
## [1.6.0](https://github.com/google/uuid/compare/v1.5.0...v1.6.0) (2024-01-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add Max UUID constant ([#149](https://github.com/google/uuid/issues/149)) ([c58770e](https://github.com/google/uuid/commit/c58770eb495f55fe2ced6284f93c5158a62e53e3))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix typo in version 7 uuid documentation ([#153](https://github.com/google/uuid/issues/153)) ([016b199](https://github.com/google/uuid/commit/016b199544692f745ffc8867b914129ecb47ef06))
|
||||
* Monotonicity in UUIDv7 ([#150](https://github.com/google/uuid/issues/150)) ([a2b2b32](https://github.com/google/uuid/commit/a2b2b32373ff0b1a312b7fdf6d38a977099698a6))
|
||||
|
||||
## [1.5.0](https://github.com/google/uuid/compare/v1.4.0...v1.5.0) (2023-12-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Validate UUID without creating new UUID ([#141](https://github.com/google/uuid/issues/141)) ([9ee7366](https://github.com/google/uuid/commit/9ee7366e66c9ad96bab89139418a713dc584ae29))
|
||||
|
||||
## [1.4.0](https://github.com/google/uuid/compare/v1.3.1...v1.4.0) (2023-10-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* UUIDs slice type with Strings() convenience method ([#133](https://github.com/google/uuid/issues/133)) ([cd5fbbd](https://github.com/google/uuid/commit/cd5fbbdd02f3e3467ac18940e07e062be1f864b4))
|
||||
|
||||
### Fixes
|
||||
|
||||
* Clarify that Parse's job is to parse but not necessarily validate strings. (Documents current behavior)
|
||||
|
||||
## [1.3.1](https://github.com/google/uuid/compare/v1.3.0...v1.3.1) (2023-08-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Use .EqualFold() to parse urn prefixed UUIDs ([#118](https://github.com/google/uuid/issues/118)) ([574e687](https://github.com/google/uuid/commit/574e6874943741fb99d41764c705173ada5293f0))
|
||||
|
||||
## Changelog
|
||||
26
vendor/github.com/google/uuid/CONTRIBUTING.md
generated
vendored
Normal file
26
vendor/github.com/google/uuid/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
# How to contribute
|
||||
|
||||
We definitely welcome patches and contribution to this project!
|
||||
|
||||
### Tips
|
||||
|
||||
Commits must be formatted according to the [Conventional Commits Specification](https://www.conventionalcommits.org).
|
||||
|
||||
Always try to include a test case! If it is not possible or not necessary,
|
||||
please explain why in the pull request description.
|
||||
|
||||
### Releasing
|
||||
|
||||
Commits that would precipitate a SemVer change, as described in the Conventional
|
||||
Commits Specification, will trigger [`release-please`](https://github.com/google-github-actions/release-please-action)
|
||||
to create a release candidate pull request. Once submitted, `release-please`
|
||||
will create a release.
|
||||
|
||||
For tips on how to work with `release-please`, see its documentation.
|
||||
|
||||
### Legal requirements
|
||||
|
||||
In order to protect both you and ourselves, you will need to sign the
|
||||
[Contributor License Agreement](https://cla.developers.google.com/clas).
|
||||
|
||||
You may have already signed it for other Google projects.
|
||||
9
vendor/github.com/google/uuid/CONTRIBUTORS
generated
vendored
Normal file
9
vendor/github.com/google/uuid/CONTRIBUTORS
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
Paul Borman <borman@google.com>
|
||||
bmatsuo
|
||||
shawnps
|
||||
theory
|
||||
jboverfelt
|
||||
dsymonds
|
||||
cd1
|
||||
wallclockbuilder
|
||||
dansouza
|
||||
27
vendor/github.com/google/uuid/LICENSE
generated
vendored
Normal file
27
vendor/github.com/google/uuid/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009,2014 Google Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
21
vendor/github.com/google/uuid/README.md
generated
vendored
Normal file
21
vendor/github.com/google/uuid/README.md
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# uuid
|
||||
The uuid package generates and inspects UUIDs based on
|
||||
[RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122)
|
||||
and DCE 1.1: Authentication and Security Services.
|
||||
|
||||
This package is based on the github.com/pborman/uuid package (previously named
|
||||
code.google.com/p/go-uuid). It differs from these earlier packages in that
|
||||
a UUID is a 16 byte array rather than a byte slice. One loss due to this
|
||||
change is the ability to represent an invalid UUID (vs a NIL UUID).
|
||||
|
||||
###### Install
|
||||
```sh
|
||||
go get github.com/google/uuid
|
||||
```
|
||||
|
||||
###### Documentation
|
||||
[](https://pkg.go.dev/github.com/google/uuid)
|
||||
|
||||
Full `go doc` style documentation for the package can be viewed online without
|
||||
installing this package by using the GoDoc site here:
|
||||
http://pkg.go.dev/github.com/google/uuid
|
||||
80
vendor/github.com/google/uuid/dce.go
generated
vendored
Normal file
80
vendor/github.com/google/uuid/dce.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// A Domain represents a Version 2 domain
|
||||
type Domain byte
|
||||
|
||||
// Domain constants for DCE Security (Version 2) UUIDs.
|
||||
const (
|
||||
Person = Domain(0)
|
||||
Group = Domain(1)
|
||||
Org = Domain(2)
|
||||
)
|
||||
|
||||
// NewDCESecurity returns a DCE Security (Version 2) UUID.
|
||||
//
|
||||
// The domain should be one of Person, Group or Org.
|
||||
// On a POSIX system the id should be the users UID for the Person
|
||||
// domain and the users GID for the Group. The meaning of id for
|
||||
// the domain Org or on non-POSIX systems is site defined.
|
||||
//
|
||||
// For a given domain/id pair the same token may be returned for up to
|
||||
// 7 minutes and 10 seconds.
|
||||
func NewDCESecurity(domain Domain, id uint32) (UUID, error) {
|
||||
uuid, err := NewUUID()
|
||||
if err == nil {
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
|
||||
uuid[9] = byte(domain)
|
||||
binary.BigEndian.PutUint32(uuid[0:], id)
|
||||
}
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
|
||||
// domain with the id returned by os.Getuid.
|
||||
//
|
||||
// NewDCESecurity(Person, uint32(os.Getuid()))
|
||||
func NewDCEPerson() (UUID, error) {
|
||||
return NewDCESecurity(Person, uint32(os.Getuid()))
|
||||
}
|
||||
|
||||
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
|
||||
// domain with the id returned by os.Getgid.
|
||||
//
|
||||
// NewDCESecurity(Group, uint32(os.Getgid()))
|
||||
func NewDCEGroup() (UUID, error) {
|
||||
return NewDCESecurity(Group, uint32(os.Getgid()))
|
||||
}
|
||||
|
||||
// Domain returns the domain for a Version 2 UUID. Domains are only defined
|
||||
// for Version 2 UUIDs.
|
||||
func (uuid UUID) Domain() Domain {
|
||||
return Domain(uuid[9])
|
||||
}
|
||||
|
||||
// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2
|
||||
// UUIDs.
|
||||
func (uuid UUID) ID() uint32 {
|
||||
return binary.BigEndian.Uint32(uuid[0:4])
|
||||
}
|
||||
|
||||
func (d Domain) String() string {
|
||||
switch d {
|
||||
case Person:
|
||||
return "Person"
|
||||
case Group:
|
||||
return "Group"
|
||||
case Org:
|
||||
return "Org"
|
||||
}
|
||||
return fmt.Sprintf("Domain%d", int(d))
|
||||
}
|
||||
12
vendor/github.com/google/uuid/doc.go
generated
vendored
Normal file
12
vendor/github.com/google/uuid/doc.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package uuid generates and inspects UUIDs.
|
||||
//
|
||||
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security
|
||||
// Services.
|
||||
//
|
||||
// A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to
|
||||
// maps or compared directly.
|
||||
package uuid
|
||||
59
vendor/github.com/google/uuid/hash.go
generated
vendored
Normal file
59
vendor/github.com/google/uuid/hash.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// Well known namespace IDs and UUIDs
|
||||
var (
|
||||
NameSpaceDNS = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NameSpaceURL = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8"))
|
||||
Nil UUID // empty UUID, all zeros
|
||||
|
||||
// The Max UUID is special form of UUID that is specified to have all 128 bits set to 1.
|
||||
Max = UUID{
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
}
|
||||
)
|
||||
|
||||
// NewHash returns a new UUID derived from the hash of space concatenated with
|
||||
// data generated by h. The hash should be at least 16 byte in length. The
|
||||
// first 16 bytes of the hash are used to form the UUID. The version of the
|
||||
// UUID will be the lower 4 bits of version. NewHash is used to implement
|
||||
// NewMD5 and NewSHA1.
|
||||
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
|
||||
h.Reset()
|
||||
h.Write(space[:]) //nolint:errcheck
|
||||
h.Write(data) //nolint:errcheck
|
||||
s := h.Sum(nil)
|
||||
var uuid UUID
|
||||
copy(uuid[:], s)
|
||||
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
|
||||
return uuid
|
||||
}
|
||||
|
||||
// NewMD5 returns a new MD5 (Version 3) UUID based on the
|
||||
// supplied name space and data. It is the same as calling:
|
||||
//
|
||||
// NewHash(md5.New(), space, data, 3)
|
||||
func NewMD5(space UUID, data []byte) UUID {
|
||||
return NewHash(md5.New(), space, data, 3)
|
||||
}
|
||||
|
||||
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
|
||||
// supplied name space and data. It is the same as calling:
|
||||
//
|
||||
// NewHash(sha1.New(), space, data, 5)
|
||||
func NewSHA1(space UUID, data []byte) UUID {
|
||||
return NewHash(sha1.New(), space, data, 5)
|
||||
}
|
||||
38
vendor/github.com/google/uuid/marshal.go
generated
vendored
Normal file
38
vendor/github.com/google/uuid/marshal.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import "fmt"
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler.
|
||||
func (uuid UUID) MarshalText() ([]byte, error) {
|
||||
var js [36]byte
|
||||
encodeHex(js[:], uuid)
|
||||
return js[:], nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (uuid *UUID) UnmarshalText(data []byte) error {
|
||||
id, err := ParseBytes(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*uuid = id
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||
func (uuid UUID) MarshalBinary() ([]byte, error) {
|
||||
return uuid[:], nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||
func (uuid *UUID) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != 16 {
|
||||
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
|
||||
}
|
||||
copy(uuid[:], data)
|
||||
return nil
|
||||
}
|
||||
90
vendor/github.com/google/uuid/node.go
generated
vendored
Normal file
90
vendor/github.com/google/uuid/node.go
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
nodeMu sync.Mutex
|
||||
ifname string // name of interface being used
|
||||
nodeID [6]byte // hardware for version 1 UUIDs
|
||||
zeroID [6]byte // nodeID with only 0's
|
||||
)
|
||||
|
||||
// NodeInterface returns the name of the interface from which the NodeID was
|
||||
// derived. The interface "user" is returned if the NodeID was set by
|
||||
// SetNodeID.
|
||||
func NodeInterface() string {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
return ifname
|
||||
}
|
||||
|
||||
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
|
||||
// If name is "" then the first usable interface found will be used or a random
|
||||
// Node ID will be generated. If a named interface cannot be found then false
|
||||
// is returned.
|
||||
//
|
||||
// SetNodeInterface never fails when name is "".
|
||||
func SetNodeInterface(name string) bool {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
return setNodeInterface(name)
|
||||
}
|
||||
|
||||
func setNodeInterface(name string) bool {
|
||||
iname, addr := getHardwareInterface(name) // null implementation for js
|
||||
if iname != "" && addr != nil {
|
||||
ifname = iname
|
||||
copy(nodeID[:], addr)
|
||||
return true
|
||||
}
|
||||
|
||||
// We found no interfaces with a valid hardware address. If name
|
||||
// does not specify a specific interface generate a random Node ID
|
||||
// (section 4.1.6)
|
||||
if name == "" {
|
||||
ifname = "random"
|
||||
randomBits(nodeID[:])
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
|
||||
// if not already set.
|
||||
func NodeID() []byte {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
if nodeID == zeroID {
|
||||
setNodeInterface("")
|
||||
}
|
||||
nid := nodeID
|
||||
return nid[:]
|
||||
}
|
||||
|
||||
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
|
||||
// of id are used. If id is less than 6 bytes then false is returned and the
|
||||
// Node ID is not set.
|
||||
func SetNodeID(id []byte) bool {
|
||||
if len(id) < 6 {
|
||||
return false
|
||||
}
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
copy(nodeID[:], id)
|
||||
ifname = "user"
|
||||
return true
|
||||
}
|
||||
|
||||
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
|
||||
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
|
||||
func (uuid UUID) NodeID() []byte {
|
||||
var node [6]byte
|
||||
copy(node[:], uuid[10:])
|
||||
return node[:]
|
||||
}
|
||||
12
vendor/github.com/google/uuid/node_js.go
generated
vendored
Normal file
12
vendor/github.com/google/uuid/node_js.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build js
|
||||
|
||||
package uuid
|
||||
|
||||
// getHardwareInterface returns nil values for the JS version of the code.
|
||||
// This removes the "net" dependency, because it is not used in the browser.
|
||||
// Using the "net" library inflates the size of the transpiled JS code by 673k bytes.
|
||||
func getHardwareInterface(name string) (string, []byte) { return "", nil }
|
||||
33
vendor/github.com/google/uuid/node_net.go
generated
vendored
Normal file
33
vendor/github.com/google/uuid/node_net.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !js
|
||||
|
||||
package uuid
|
||||
|
||||
import "net"
|
||||
|
||||
var interfaces []net.Interface // cached list of interfaces
|
||||
|
||||
// getHardwareInterface returns the name and hardware address of interface name.
|
||||
// If name is "" then the name and hardware address of one of the system's
|
||||
// interfaces is returned. If no interfaces are found (name does not exist or
|
||||
// there are no interfaces) then "", nil is returned.
|
||||
//
|
||||
// Only addresses of at least 6 bytes are returned.
|
||||
func getHardwareInterface(name string) (string, []byte) {
|
||||
if interfaces == nil {
|
||||
var err error
|
||||
interfaces, err = net.Interfaces()
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
for _, ifs := range interfaces {
|
||||
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
|
||||
return ifs.Name, ifs.HardwareAddr
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
118
vendor/github.com/google/uuid/null.go
generated
vendored
Normal file
118
vendor/github.com/google/uuid/null.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright 2021 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var jsonNull = []byte("null")
|
||||
|
||||
// NullUUID represents a UUID that may be null.
|
||||
// NullUUID implements the SQL driver.Scanner interface so
|
||||
// it can be used as a scan destination:
|
||||
//
|
||||
// var u uuid.NullUUID
|
||||
// err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&u)
|
||||
// ...
|
||||
// if u.Valid {
|
||||
// // use u.UUID
|
||||
// } else {
|
||||
// // NULL value
|
||||
// }
|
||||
//
|
||||
type NullUUID struct {
|
||||
UUID UUID
|
||||
Valid bool // Valid is true if UUID is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the SQL driver.Scanner interface.
|
||||
func (nu *NullUUID) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
nu.UUID, nu.Valid = Nil, false
|
||||
return nil
|
||||
}
|
||||
|
||||
err := nu.UUID.Scan(value)
|
||||
if err != nil {
|
||||
nu.Valid = false
|
||||
return err
|
||||
}
|
||||
|
||||
nu.Valid = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (nu NullUUID) Value() (driver.Value, error) {
|
||||
if !nu.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
// Delegate to UUID Value function
|
||||
return nu.UUID.Value()
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||
func (nu NullUUID) MarshalBinary() ([]byte, error) {
|
||||
if nu.Valid {
|
||||
return nu.UUID[:], nil
|
||||
}
|
||||
|
||||
return []byte(nil), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||
func (nu *NullUUID) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != 16 {
|
||||
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
|
||||
}
|
||||
copy(nu.UUID[:], data)
|
||||
nu.Valid = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler.
|
||||
func (nu NullUUID) MarshalText() ([]byte, error) {
|
||||
if nu.Valid {
|
||||
return nu.UUID.MarshalText()
|
||||
}
|
||||
|
||||
return jsonNull, nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (nu *NullUUID) UnmarshalText(data []byte) error {
|
||||
id, err := ParseBytes(data)
|
||||
if err != nil {
|
||||
nu.Valid = false
|
||||
return err
|
||||
}
|
||||
nu.UUID = id
|
||||
nu.Valid = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (nu NullUUID) MarshalJSON() ([]byte, error) {
|
||||
if nu.Valid {
|
||||
return json.Marshal(nu.UUID)
|
||||
}
|
||||
|
||||
return jsonNull, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (nu *NullUUID) UnmarshalJSON(data []byte) error {
|
||||
if bytes.Equal(data, jsonNull) {
|
||||
*nu = NullUUID{}
|
||||
return nil // valid null UUID
|
||||
}
|
||||
err := json.Unmarshal(data, &nu.UUID)
|
||||
nu.Valid = err == nil
|
||||
return err
|
||||
}
|
||||
59
vendor/github.com/google/uuid/sql.go
generated
vendored
Normal file
59
vendor/github.com/google/uuid/sql.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Scan implements sql.Scanner so UUIDs can be read from databases transparently.
|
||||
// Currently, database types that map to string and []byte are supported. Please
|
||||
// consult database-specific driver documentation for matching types.
|
||||
func (uuid *UUID) Scan(src interface{}) error {
|
||||
switch src := src.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
|
||||
case string:
|
||||
// if an empty UUID comes from a table, we return a null UUID
|
||||
if src == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// see Parse for required string format
|
||||
u, err := Parse(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Scan: %v", err)
|
||||
}
|
||||
|
||||
*uuid = u
|
||||
|
||||
case []byte:
|
||||
// if an empty UUID comes from a table, we return a null UUID
|
||||
if len(src) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// assumes a simple slice of bytes if 16 bytes
|
||||
// otherwise attempts to parse
|
||||
if len(src) != 16 {
|
||||
return uuid.Scan(string(src))
|
||||
}
|
||||
copy((*uuid)[:], src)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("Scan: unable to scan type %T into UUID", src)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements sql.Valuer so that UUIDs can be written to databases
|
||||
// transparently. Currently, UUIDs map to strings. Please consult
|
||||
// database-specific driver documentation for matching types.
|
||||
func (uuid UUID) Value() (driver.Value, error) {
|
||||
return uuid.String(), nil
|
||||
}
|
||||
134
vendor/github.com/google/uuid/time.go
generated
vendored
Normal file
134
vendor/github.com/google/uuid/time.go
generated
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
|
||||
// 1582.
|
||||
type Time int64
|
||||
|
||||
const (
|
||||
lillian = 2299160 // Julian day of 15 Oct 1582
|
||||
unix = 2440587 // Julian day of 1 Jan 1970
|
||||
epoch = unix - lillian // Days between epochs
|
||||
g1582 = epoch * 86400 // seconds between epochs
|
||||
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
|
||||
)
|
||||
|
||||
var (
|
||||
timeMu sync.Mutex
|
||||
lasttime uint64 // last time we returned
|
||||
clockSeq uint16 // clock sequence for this run
|
||||
|
||||
timeNow = time.Now // for testing
|
||||
)
|
||||
|
||||
// UnixTime converts t the number of seconds and nanoseconds using the Unix
|
||||
// epoch of 1 Jan 1970.
|
||||
func (t Time) UnixTime() (sec, nsec int64) {
|
||||
sec = int64(t - g1582ns100)
|
||||
nsec = (sec % 10000000) * 100
|
||||
sec /= 10000000
|
||||
return sec, nsec
|
||||
}
|
||||
|
||||
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
|
||||
// clock sequence as well as adjusting the clock sequence as needed. An error
|
||||
// is returned if the current time cannot be determined.
|
||||
func GetTime() (Time, uint16, error) {
|
||||
defer timeMu.Unlock()
|
||||
timeMu.Lock()
|
||||
return getTime()
|
||||
}
|
||||
|
||||
func getTime() (Time, uint16, error) {
|
||||
t := timeNow()
|
||||
|
||||
// If we don't have a clock sequence already, set one.
|
||||
if clockSeq == 0 {
|
||||
setClockSequence(-1)
|
||||
}
|
||||
now := uint64(t.UnixNano()/100) + g1582ns100
|
||||
|
||||
// If time has gone backwards with this clock sequence then we
|
||||
// increment the clock sequence
|
||||
if now <= lasttime {
|
||||
clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000
|
||||
}
|
||||
lasttime = now
|
||||
return Time(now), clockSeq, nil
|
||||
}
|
||||
|
||||
// ClockSequence returns the current clock sequence, generating one if not
|
||||
// already set. The clock sequence is only used for Version 1 UUIDs.
|
||||
//
|
||||
// The uuid package does not use global static storage for the clock sequence or
|
||||
// the last time a UUID was generated. Unless SetClockSequence is used, a new
|
||||
// random clock sequence is generated the first time a clock sequence is
|
||||
// requested by ClockSequence, GetTime, or NewUUID. (section 4.2.1.1)
|
||||
func ClockSequence() int {
|
||||
defer timeMu.Unlock()
|
||||
timeMu.Lock()
|
||||
return clockSequence()
|
||||
}
|
||||
|
||||
func clockSequence() int {
|
||||
if clockSeq == 0 {
|
||||
setClockSequence(-1)
|
||||
}
|
||||
return int(clockSeq & 0x3fff)
|
||||
}
|
||||
|
||||
// SetClockSequence sets the clock sequence to the lower 14 bits of seq. Setting to
|
||||
// -1 causes a new sequence to be generated.
|
||||
func SetClockSequence(seq int) {
|
||||
defer timeMu.Unlock()
|
||||
timeMu.Lock()
|
||||
setClockSequence(seq)
|
||||
}
|
||||
|
||||
func setClockSequence(seq int) {
|
||||
if seq == -1 {
|
||||
var b [2]byte
|
||||
randomBits(b[:]) // clock sequence
|
||||
seq = int(b[0])<<8 | int(b[1])
|
||||
}
|
||||
oldSeq := clockSeq
|
||||
clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant
|
||||
if oldSeq != clockSeq {
|
||||
lasttime = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
|
||||
// uuid. The time is only defined for version 1, 2, 6 and 7 UUIDs.
|
||||
func (uuid UUID) Time() Time {
|
||||
var t Time
|
||||
switch uuid.Version() {
|
||||
case 6:
|
||||
time := binary.BigEndian.Uint64(uuid[:8]) // Ignore uuid[6] version b0110
|
||||
t = Time(time)
|
||||
case 7:
|
||||
time := binary.BigEndian.Uint64(uuid[:8])
|
||||
t = Time((time>>16)*10000 + g1582ns100)
|
||||
default: // forward compatible
|
||||
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
|
||||
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
|
||||
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
|
||||
t = Time(time)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// ClockSequence returns the clock sequence encoded in uuid.
|
||||
// The clock sequence is only well defined for version 1 and 2 UUIDs.
|
||||
func (uuid UUID) ClockSequence() int {
|
||||
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff
|
||||
}
|
||||
43
vendor/github.com/google/uuid/util.go
generated
vendored
Normal file
43
vendor/github.com/google/uuid/util.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// randomBits completely fills slice b with random data.
|
||||
func randomBits(b []byte) {
|
||||
if _, err := io.ReadFull(rander, b); err != nil {
|
||||
panic(err.Error()) // rand should never fail
|
||||
}
|
||||
}
|
||||
|
||||
// xvalues returns the value of a byte as a hexadecimal digit or 255.
|
||||
var xvalues = [256]byte{
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
|
||||
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
}
|
||||
|
||||
// xtob converts hex characters x1 and x2 into a byte.
|
||||
func xtob(x1, x2 byte) (byte, bool) {
|
||||
b1 := xvalues[x1]
|
||||
b2 := xvalues[x2]
|
||||
return (b1 << 4) | b2, b1 != 255 && b2 != 255
|
||||
}
|
||||
365
vendor/github.com/google/uuid/uuid.go
generated
vendored
Normal file
365
vendor/github.com/google/uuid/uuid.go
generated
vendored
Normal file
@@ -0,0 +1,365 @@
|
||||
// Copyright 2018 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
|
||||
// 4122.
|
||||
type UUID [16]byte
|
||||
|
||||
// A Version represents a UUID's version.
|
||||
type Version byte
|
||||
|
||||
// A Variant represents a UUID's variant.
|
||||
type Variant byte
|
||||
|
||||
// Constants returned by Variant.
|
||||
const (
|
||||
Invalid = Variant(iota) // Invalid UUID
|
||||
RFC4122 // The variant specified in RFC4122
|
||||
Reserved // Reserved, NCS backward compatibility.
|
||||
Microsoft // Reserved, Microsoft Corporation backward compatibility.
|
||||
Future // Reserved for future definition.
|
||||
)
|
||||
|
||||
const randPoolSize = 16 * 16
|
||||
|
||||
var (
|
||||
rander = rand.Reader // random function
|
||||
poolEnabled = false
|
||||
poolMu sync.Mutex
|
||||
poolPos = randPoolSize // protected with poolMu
|
||||
pool [randPoolSize]byte // protected with poolMu
|
||||
)
|
||||
|
||||
type invalidLengthError struct{ len int }
|
||||
|
||||
func (err invalidLengthError) Error() string {
|
||||
return fmt.Sprintf("invalid UUID length: %d", err.len)
|
||||
}
|
||||
|
||||
// IsInvalidLengthError is matcher function for custom error invalidLengthError
|
||||
func IsInvalidLengthError(err error) bool {
|
||||
_, ok := err.(invalidLengthError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Parse decodes s into a UUID or returns an error if it cannot be parsed. Both
|
||||
// the standard UUID forms defined in RFC 4122
|
||||
// (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) are decoded. In addition,
|
||||
// Parse accepts non-standard strings such as the raw hex encoding
|
||||
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx and 38 byte "Microsoft style" encodings,
|
||||
// e.g. {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}. Only the middle 36 bytes are
|
||||
// examined in the latter case. Parse should not be used to validate strings as
|
||||
// it parses non-standard encodings as indicated above.
|
||||
func Parse(s string) (UUID, error) {
|
||||
var uuid UUID
|
||||
switch len(s) {
|
||||
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
case 36:
|
||||
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
case 36 + 9:
|
||||
if !strings.EqualFold(s[:9], "urn:uuid:") {
|
||||
return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9])
|
||||
}
|
||||
s = s[9:]
|
||||
|
||||
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||
case 36 + 2:
|
||||
s = s[1:]
|
||||
|
||||
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
case 32:
|
||||
var ok bool
|
||||
for i := range uuid {
|
||||
uuid[i], ok = xtob(s[i*2], s[i*2+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
}
|
||||
return uuid, nil
|
||||
default:
|
||||
return uuid, invalidLengthError{len(s)}
|
||||
}
|
||||
// s is now at least 36 bytes long
|
||||
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
for i, x := range [16]int{
|
||||
0, 2, 4, 6,
|
||||
9, 11,
|
||||
14, 16,
|
||||
19, 21,
|
||||
24, 26, 28, 30, 32, 34,
|
||||
} {
|
||||
v, ok := xtob(s[x], s[x+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
uuid[i] = v
|
||||
}
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// ParseBytes is like Parse, except it parses a byte slice instead of a string.
|
||||
func ParseBytes(b []byte) (UUID, error) {
|
||||
var uuid UUID
|
||||
switch len(b) {
|
||||
case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
if !bytes.EqualFold(b[:9], []byte("urn:uuid:")) {
|
||||
return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9])
|
||||
}
|
||||
b = b[9:]
|
||||
case 36 + 2: // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||
b = b[1:]
|
||||
case 32: // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
var ok bool
|
||||
for i := 0; i < 32; i += 2 {
|
||||
uuid[i/2], ok = xtob(b[i], b[i+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
}
|
||||
return uuid, nil
|
||||
default:
|
||||
return uuid, invalidLengthError{len(b)}
|
||||
}
|
||||
// s is now at least 36 bytes long
|
||||
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
for i, x := range [16]int{
|
||||
0, 2, 4, 6,
|
||||
9, 11,
|
||||
14, 16,
|
||||
19, 21,
|
||||
24, 26, 28, 30, 32, 34,
|
||||
} {
|
||||
v, ok := xtob(b[x], b[x+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
uuid[i] = v
|
||||
}
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// MustParse is like Parse but panics if the string cannot be parsed.
|
||||
// It simplifies safe initialization of global variables holding compiled UUIDs.
|
||||
func MustParse(s string) UUID {
|
||||
uuid, err := Parse(s)
|
||||
if err != nil {
|
||||
panic(`uuid: Parse(` + s + `): ` + err.Error())
|
||||
}
|
||||
return uuid
|
||||
}
|
||||
|
||||
// FromBytes creates a new UUID from a byte slice. Returns an error if the slice
|
||||
// does not have a length of 16. The bytes are copied from the slice.
|
||||
func FromBytes(b []byte) (uuid UUID, err error) {
|
||||
err = uuid.UnmarshalBinary(b)
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
// Must returns uuid if err is nil and panics otherwise.
|
||||
func Must(uuid UUID, err error) UUID {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return uuid
|
||||
}
|
||||
|
||||
// Validate returns an error if s is not a properly formatted UUID in one of the following formats:
|
||||
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||
// It returns an error if the format is invalid, otherwise nil.
|
||||
func Validate(s string) error {
|
||||
switch len(s) {
|
||||
// Standard UUID format
|
||||
case 36:
|
||||
|
||||
// UUID with "urn:uuid:" prefix
|
||||
case 36 + 9:
|
||||
if !strings.EqualFold(s[:9], "urn:uuid:") {
|
||||
return fmt.Errorf("invalid urn prefix: %q", s[:9])
|
||||
}
|
||||
s = s[9:]
|
||||
|
||||
// UUID enclosed in braces
|
||||
case 36 + 2:
|
||||
if s[0] != '{' || s[len(s)-1] != '}' {
|
||||
return fmt.Errorf("invalid bracketed UUID format")
|
||||
}
|
||||
s = s[1 : len(s)-1]
|
||||
|
||||
// UUID without hyphens
|
||||
case 32:
|
||||
for i := 0; i < len(s); i += 2 {
|
||||
_, ok := xtob(s[i], s[i+1])
|
||||
if !ok {
|
||||
return errors.New("invalid UUID format")
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return invalidLengthError{len(s)}
|
||||
}
|
||||
|
||||
// Check for standard UUID format
|
||||
if len(s) == 36 {
|
||||
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||
return errors.New("invalid UUID format")
|
||||
}
|
||||
for _, x := range []int{0, 2, 4, 6, 9, 11, 14, 16, 19, 21, 24, 26, 28, 30, 32, 34} {
|
||||
if _, ok := xtob(s[x], s[x+1]); !ok {
|
||||
return errors.New("invalid UUID format")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
// , or "" if uuid is invalid.
|
||||
func (uuid UUID) String() string {
|
||||
var buf [36]byte
|
||||
encodeHex(buf[:], uuid)
|
||||
return string(buf[:])
|
||||
}
|
||||
|
||||
// URN returns the RFC 2141 URN form of uuid,
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
|
||||
func (uuid UUID) URN() string {
|
||||
var buf [36 + 9]byte
|
||||
copy(buf[:], "urn:uuid:")
|
||||
encodeHex(buf[9:], uuid)
|
||||
return string(buf[:])
|
||||
}
|
||||
|
||||
func encodeHex(dst []byte, uuid UUID) {
|
||||
hex.Encode(dst, uuid[:4])
|
||||
dst[8] = '-'
|
||||
hex.Encode(dst[9:13], uuid[4:6])
|
||||
dst[13] = '-'
|
||||
hex.Encode(dst[14:18], uuid[6:8])
|
||||
dst[18] = '-'
|
||||
hex.Encode(dst[19:23], uuid[8:10])
|
||||
dst[23] = '-'
|
||||
hex.Encode(dst[24:], uuid[10:])
|
||||
}
|
||||
|
||||
// Variant returns the variant encoded in uuid.
|
||||
func (uuid UUID) Variant() Variant {
|
||||
switch {
|
||||
case (uuid[8] & 0xc0) == 0x80:
|
||||
return RFC4122
|
||||
case (uuid[8] & 0xe0) == 0xc0:
|
||||
return Microsoft
|
||||
case (uuid[8] & 0xe0) == 0xe0:
|
||||
return Future
|
||||
default:
|
||||
return Reserved
|
||||
}
|
||||
}
|
||||
|
||||
// Version returns the version of uuid.
|
||||
func (uuid UUID) Version() Version {
|
||||
return Version(uuid[6] >> 4)
|
||||
}
|
||||
|
||||
func (v Version) String() string {
|
||||
if v > 15 {
|
||||
return fmt.Sprintf("BAD_VERSION_%d", v)
|
||||
}
|
||||
return fmt.Sprintf("VERSION_%d", v)
|
||||
}
|
||||
|
||||
func (v Variant) String() string {
|
||||
switch v {
|
||||
case RFC4122:
|
||||
return "RFC4122"
|
||||
case Reserved:
|
||||
return "Reserved"
|
||||
case Microsoft:
|
||||
return "Microsoft"
|
||||
case Future:
|
||||
return "Future"
|
||||
case Invalid:
|
||||
return "Invalid"
|
||||
}
|
||||
return fmt.Sprintf("BadVariant%d", int(v))
|
||||
}
|
||||
|
||||
// SetRand sets the random number generator to r, which implements io.Reader.
|
||||
// If r.Read returns an error when the package requests random data then
|
||||
// a panic will be issued.
|
||||
//
|
||||
// Calling SetRand with nil sets the random number generator to the default
|
||||
// generator.
|
||||
func SetRand(r io.Reader) {
|
||||
if r == nil {
|
||||
rander = rand.Reader
|
||||
return
|
||||
}
|
||||
rander = r
|
||||
}
|
||||
|
||||
// EnableRandPool enables internal randomness pool used for Random
|
||||
// (Version 4) UUID generation. The pool contains random bytes read from
|
||||
// the random number generator on demand in batches. Enabling the pool
|
||||
// may improve the UUID generation throughput significantly.
|
||||
//
|
||||
// Since the pool is stored on the Go heap, this feature may be a bad fit
|
||||
// for security sensitive applications.
|
||||
//
|
||||
// Both EnableRandPool and DisableRandPool are not thread-safe and should
|
||||
// only be called when there is no possibility that New or any other
|
||||
// UUID Version 4 generation function will be called concurrently.
|
||||
func EnableRandPool() {
|
||||
poolEnabled = true
|
||||
}
|
||||
|
||||
// DisableRandPool disables the randomness pool if it was previously
|
||||
// enabled with EnableRandPool.
|
||||
//
|
||||
// Both EnableRandPool and DisableRandPool are not thread-safe and should
|
||||
// only be called when there is no possibility that New or any other
|
||||
// UUID Version 4 generation function will be called concurrently.
|
||||
func DisableRandPool() {
|
||||
poolEnabled = false
|
||||
defer poolMu.Unlock()
|
||||
poolMu.Lock()
|
||||
poolPos = randPoolSize
|
||||
}
|
||||
|
||||
// UUIDs is a slice of UUID types.
|
||||
type UUIDs []UUID
|
||||
|
||||
// Strings returns a string slice containing the string form of each UUID in uuids.
|
||||
func (uuids UUIDs) Strings() []string {
|
||||
var uuidStrs = make([]string, len(uuids))
|
||||
for i, uuid := range uuids {
|
||||
uuidStrs[i] = uuid.String()
|
||||
}
|
||||
return uuidStrs
|
||||
}
|
||||
44
vendor/github.com/google/uuid/version1.go
generated
vendored
Normal file
44
vendor/github.com/google/uuid/version1.go
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
|
||||
// sequence, and the current time. If the NodeID has not been set by SetNodeID
|
||||
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
|
||||
// be set NewUUID returns nil. If clock sequence has not been set by
|
||||
// SetClockSequence then it will be set automatically. If GetTime fails to
|
||||
// return the current NewUUID returns nil and an error.
|
||||
//
|
||||
// In most cases, New should be used.
|
||||
func NewUUID() (UUID, error) {
|
||||
var uuid UUID
|
||||
now, seq, err := GetTime()
|
||||
if err != nil {
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
timeLow := uint32(now & 0xffffffff)
|
||||
timeMid := uint16((now >> 32) & 0xffff)
|
||||
timeHi := uint16((now >> 48) & 0x0fff)
|
||||
timeHi |= 0x1000 // Version 1
|
||||
|
||||
binary.BigEndian.PutUint32(uuid[0:], timeLow)
|
||||
binary.BigEndian.PutUint16(uuid[4:], timeMid)
|
||||
binary.BigEndian.PutUint16(uuid[6:], timeHi)
|
||||
binary.BigEndian.PutUint16(uuid[8:], seq)
|
||||
|
||||
nodeMu.Lock()
|
||||
if nodeID == zeroID {
|
||||
setNodeInterface("")
|
||||
}
|
||||
copy(uuid[10:], nodeID[:])
|
||||
nodeMu.Unlock()
|
||||
|
||||
return uuid, nil
|
||||
}
|
||||
76
vendor/github.com/google/uuid/version4.go
generated
vendored
Normal file
76
vendor/github.com/google/uuid/version4.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import "io"
|
||||
|
||||
// New creates a new random UUID or panics. New is equivalent to
|
||||
// the expression
|
||||
//
|
||||
// uuid.Must(uuid.NewRandom())
|
||||
func New() UUID {
|
||||
return Must(NewRandom())
|
||||
}
|
||||
|
||||
// NewString creates a new random UUID and returns it as a string or panics.
|
||||
// NewString is equivalent to the expression
|
||||
//
|
||||
// uuid.New().String()
|
||||
func NewString() string {
|
||||
return Must(NewRandom()).String()
|
||||
}
|
||||
|
||||
// NewRandom returns a Random (Version 4) UUID.
|
||||
//
|
||||
// The strength of the UUIDs is based on the strength of the crypto/rand
|
||||
// package.
|
||||
//
|
||||
// Uses the randomness pool if it was enabled with EnableRandPool.
|
||||
//
|
||||
// A note about uniqueness derived from the UUID Wikipedia entry:
|
||||
//
|
||||
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
|
||||
// hit by a meteorite is estimated to be one chance in 17 billion, that
|
||||
// means the probability is about 0.00000000006 (6 × 10−11),
|
||||
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
|
||||
// year and having one duplicate.
|
||||
func NewRandom() (UUID, error) {
|
||||
if !poolEnabled {
|
||||
return NewRandomFromReader(rander)
|
||||
}
|
||||
return newRandomFromPool()
|
||||
}
|
||||
|
||||
// NewRandomFromReader returns a UUID based on bytes read from a given io.Reader.
|
||||
func NewRandomFromReader(r io.Reader) (UUID, error) {
|
||||
var uuid UUID
|
||||
_, err := io.ReadFull(r, uuid[:])
|
||||
if err != nil {
|
||||
return Nil, err
|
||||
}
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
func newRandomFromPool() (UUID, error) {
|
||||
var uuid UUID
|
||||
poolMu.Lock()
|
||||
if poolPos == randPoolSize {
|
||||
_, err := io.ReadFull(rander, pool[:])
|
||||
if err != nil {
|
||||
poolMu.Unlock()
|
||||
return Nil, err
|
||||
}
|
||||
poolPos = 0
|
||||
}
|
||||
copy(uuid[:], pool[poolPos:(poolPos+16)])
|
||||
poolPos += 16
|
||||
poolMu.Unlock()
|
||||
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
|
||||
return uuid, nil
|
||||
}
|
||||
56
vendor/github.com/google/uuid/version6.go
generated
vendored
Normal file
56
vendor/github.com/google/uuid/version6.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2023 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
// UUID version 6 is a field-compatible version of UUIDv1, reordered for improved DB locality.
|
||||
// It is expected that UUIDv6 will primarily be used in contexts where there are existing v1 UUIDs.
|
||||
// Systems that do not involve legacy UUIDv1 SHOULD consider using UUIDv7 instead.
|
||||
//
|
||||
// see https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03#uuidv6
|
||||
//
|
||||
// NewV6 returns a Version 6 UUID based on the current NodeID and clock
|
||||
// sequence, and the current time. If the NodeID has not been set by SetNodeID
|
||||
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
|
||||
// be set NewV6 set NodeID is random bits automatically . If clock sequence has not been set by
|
||||
// SetClockSequence then it will be set automatically. If GetTime fails to
|
||||
// return the current NewV6 returns Nil and an error.
|
||||
func NewV6() (UUID, error) {
|
||||
var uuid UUID
|
||||
now, seq, err := GetTime()
|
||||
if err != nil {
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
/*
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| time_high |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| time_mid | time_low_and_version |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|clk_seq_hi_res | clk_seq_low | node (0-1) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| node (2-5) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
|
||||
binary.BigEndian.PutUint64(uuid[0:], uint64(now))
|
||||
binary.BigEndian.PutUint16(uuid[8:], seq)
|
||||
|
||||
uuid[6] = 0x60 | (uuid[6] & 0x0F)
|
||||
uuid[8] = 0x80 | (uuid[8] & 0x3F)
|
||||
|
||||
nodeMu.Lock()
|
||||
if nodeID == zeroID {
|
||||
setNodeInterface("")
|
||||
}
|
||||
copy(uuid[10:], nodeID[:])
|
||||
nodeMu.Unlock()
|
||||
|
||||
return uuid, nil
|
||||
}
|
||||
104
vendor/github.com/google/uuid/version7.go
generated
vendored
Normal file
104
vendor/github.com/google/uuid/version7.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright 2023 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// UUID version 7 features a time-ordered value field derived from the widely
|
||||
// implemented and well known Unix Epoch timestamp source,
|
||||
// the number of milliseconds seconds since midnight 1 Jan 1970 UTC, leap seconds excluded.
|
||||
// As well as improved entropy characteristics over versions 1 or 6.
|
||||
//
|
||||
// see https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03#name-uuid-version-7
|
||||
//
|
||||
// Implementations SHOULD utilize UUID version 7 over UUID version 1 and 6 if possible.
|
||||
//
|
||||
// NewV7 returns a Version 7 UUID based on the current time(Unix Epoch).
|
||||
// Uses the randomness pool if it was enabled with EnableRandPool.
|
||||
// On error, NewV7 returns Nil and an error
|
||||
func NewV7() (UUID, error) {
|
||||
uuid, err := NewRandom()
|
||||
if err != nil {
|
||||
return uuid, err
|
||||
}
|
||||
makeV7(uuid[:])
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// NewV7FromReader returns a Version 7 UUID based on the current time(Unix Epoch).
|
||||
// it use NewRandomFromReader fill random bits.
|
||||
// On error, NewV7FromReader returns Nil and an error.
|
||||
func NewV7FromReader(r io.Reader) (UUID, error) {
|
||||
uuid, err := NewRandomFromReader(r)
|
||||
if err != nil {
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
makeV7(uuid[:])
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// makeV7 fill 48 bits time (uuid[0] - uuid[5]), set version b0111 (uuid[6])
|
||||
// uuid[8] already has the right version number (Variant is 10)
|
||||
// see function NewV7 and NewV7FromReader
|
||||
func makeV7(uuid []byte) {
|
||||
/*
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| unix_ts_ms |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| unix_ts_ms | ver | rand_a (12 bit seq) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|var| rand_b |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| rand_b |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
_ = uuid[15] // bounds check
|
||||
|
||||
t, s := getV7Time()
|
||||
|
||||
uuid[0] = byte(t >> 40)
|
||||
uuid[1] = byte(t >> 32)
|
||||
uuid[2] = byte(t >> 24)
|
||||
uuid[3] = byte(t >> 16)
|
||||
uuid[4] = byte(t >> 8)
|
||||
uuid[5] = byte(t)
|
||||
|
||||
uuid[6] = 0x70 | (0x0F & byte(s>>8))
|
||||
uuid[7] = byte(s)
|
||||
}
|
||||
|
||||
// lastV7time is the last time we returned stored as:
|
||||
//
|
||||
// 52 bits of time in milliseconds since epoch
|
||||
// 12 bits of (fractional nanoseconds) >> 8
|
||||
var lastV7time int64
|
||||
|
||||
const nanoPerMilli = 1000000
|
||||
|
||||
// getV7Time returns the time in milliseconds and nanoseconds / 256.
|
||||
// The returned (milli << 12 + seq) is guarenteed to be greater than
|
||||
// (milli << 12 + seq) returned by any previous call to getV7Time.
|
||||
func getV7Time() (milli, seq int64) {
|
||||
timeMu.Lock()
|
||||
defer timeMu.Unlock()
|
||||
|
||||
nano := timeNow().UnixNano()
|
||||
milli = nano / nanoPerMilli
|
||||
// Sequence number is between 0 and 3906 (nanoPerMilli>>8)
|
||||
seq = (nano - milli*nanoPerMilli) >> 8
|
||||
now := milli<<12 + seq
|
||||
if now <= lastV7time {
|
||||
now = lastV7time + 1
|
||||
milli = now >> 12
|
||||
seq = now & 0xfff
|
||||
}
|
||||
lastV7time = now
|
||||
return milli, seq
|
||||
}
|
||||
9
vendor/github.com/mattn/go-isatty/LICENSE
generated
vendored
Normal file
9
vendor/github.com/mattn/go-isatty/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
Copyright (c) Yasuhiro MATSUMOTO <mattn.jp@gmail.com>
|
||||
|
||||
MIT License (Expat)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
50
vendor/github.com/mattn/go-isatty/README.md
generated
vendored
Normal file
50
vendor/github.com/mattn/go-isatty/README.md
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
# go-isatty
|
||||
|
||||
[](http://godoc.org/github.com/mattn/go-isatty)
|
||||
[](https://codecov.io/gh/mattn/go-isatty)
|
||||
[](https://coveralls.io/github/mattn/go-isatty?branch=master)
|
||||
[](https://goreportcard.com/report/mattn/go-isatty)
|
||||
|
||||
isatty for golang
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mattn/go-isatty"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
fmt.Println("Is Terminal")
|
||||
} else if isatty.IsCygwinTerminal(os.Stdout.Fd()) {
|
||||
fmt.Println("Is Cygwin/MSYS2 Terminal")
|
||||
} else {
|
||||
fmt.Println("Is Not Terminal")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
$ go get github.com/mattn/go-isatty
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Author
|
||||
|
||||
Yasuhiro Matsumoto (a.k.a mattn)
|
||||
|
||||
## Thanks
|
||||
|
||||
* k-takata: base idea for IsCygwinTerminal
|
||||
|
||||
https://github.com/k-takata/go-iscygpty
|
||||
2
vendor/github.com/mattn/go-isatty/doc.go
generated
vendored
Normal file
2
vendor/github.com/mattn/go-isatty/doc.go
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
// Package isatty implements interface to isatty
|
||||
package isatty
|
||||
12
vendor/github.com/mattn/go-isatty/go.test.sh
generated
vendored
Normal file
12
vendor/github.com/mattn/go-isatty/go.test.sh
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
echo "" > coverage.txt
|
||||
|
||||
for d in $(go list ./... | grep -v vendor); do
|
||||
go test -race -coverprofile=profile.out -covermode=atomic "$d"
|
||||
if [ -f profile.out ]; then
|
||||
cat profile.out >> coverage.txt
|
||||
rm profile.out
|
||||
fi
|
||||
done
|
||||
20
vendor/github.com/mattn/go-isatty/isatty_bsd.go
generated
vendored
Normal file
20
vendor/github.com/mattn/go-isatty/isatty_bsd.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
//go:build (darwin || freebsd || openbsd || netbsd || dragonfly || hurd) && !appengine && !tinygo
|
||||
// +build darwin freebsd openbsd netbsd dragonfly hurd
|
||||
// +build !appengine
|
||||
// +build !tinygo
|
||||
|
||||
package isatty
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
// IsTerminal return true if the file descriptor is terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
_, err := unix.IoctlGetTermios(int(fd), unix.TIOCGETA)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
||||
17
vendor/github.com/mattn/go-isatty/isatty_others.go
generated
vendored
Normal file
17
vendor/github.com/mattn/go-isatty/isatty_others.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
//go:build (appengine || js || nacl || tinygo || wasm) && !windows
|
||||
// +build appengine js nacl tinygo wasm
|
||||
// +build !windows
|
||||
|
||||
package isatty
|
||||
|
||||
// IsTerminal returns true if the file descriptor is terminal which
|
||||
// is always false on js and appengine classic which is a sandboxed PaaS.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
||||
23
vendor/github.com/mattn/go-isatty/isatty_plan9.go
generated
vendored
Normal file
23
vendor/github.com/mattn/go-isatty/isatty_plan9.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
//go:build plan9
|
||||
// +build plan9
|
||||
|
||||
package isatty
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
path, err := syscall.Fd2path(int(fd))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return path == "/dev/cons" || path == "/mnt/term/dev/cons"
|
||||
}
|
||||
|
||||
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
||||
21
vendor/github.com/mattn/go-isatty/isatty_solaris.go
generated
vendored
Normal file
21
vendor/github.com/mattn/go-isatty/isatty_solaris.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
//go:build solaris && !appengine
|
||||
// +build solaris,!appengine
|
||||
|
||||
package isatty
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
// see: https://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libc/port/gen/isatty.c
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
_, err := unix.IoctlGetTermio(int(fd), unix.TCGETA)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
||||
20
vendor/github.com/mattn/go-isatty/isatty_tcgets.go
generated
vendored
Normal file
20
vendor/github.com/mattn/go-isatty/isatty_tcgets.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
//go:build (linux || aix || zos) && !appengine && !tinygo
|
||||
// +build linux aix zos
|
||||
// +build !appengine
|
||||
// +build !tinygo
|
||||
|
||||
package isatty
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
// IsTerminal return true if the file descriptor is terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
_, err := unix.IoctlGetTermios(int(fd), unix.TCGETS)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
||||
125
vendor/github.com/mattn/go-isatty/isatty_windows.go
generated
vendored
Normal file
125
vendor/github.com/mattn/go-isatty/isatty_windows.go
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
//go:build windows && !appengine
|
||||
// +build windows,!appengine
|
||||
|
||||
package isatty
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
objectNameInfo uintptr = 1
|
||||
fileNameInfo = 2
|
||||
fileTypePipe = 3
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
ntdll = syscall.NewLazyDLL("ntdll.dll")
|
||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||
procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx")
|
||||
procGetFileType = kernel32.NewProc("GetFileType")
|
||||
procNtQueryObject = ntdll.NewProc("NtQueryObject")
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Check if GetFileInformationByHandleEx is available.
|
||||
if procGetFileInformationByHandleEx.Find() != nil {
|
||||
procGetFileInformationByHandleEx = nil
|
||||
}
|
||||
}
|
||||
|
||||
// IsTerminal return true if the file descriptor is terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
var st uint32
|
||||
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0)
|
||||
return r != 0 && e == 0
|
||||
}
|
||||
|
||||
// Check pipe name is used for cygwin/msys2 pty.
|
||||
// Cygwin/MSYS2 PTY has a name like:
|
||||
// \{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master
|
||||
func isCygwinPipeName(name string) bool {
|
||||
token := strings.Split(name, "-")
|
||||
if len(token) < 5 {
|
||||
return false
|
||||
}
|
||||
|
||||
if token[0] != `\msys` &&
|
||||
token[0] != `\cygwin` &&
|
||||
token[0] != `\Device\NamedPipe\msys` &&
|
||||
token[0] != `\Device\NamedPipe\cygwin` {
|
||||
return false
|
||||
}
|
||||
|
||||
if token[1] == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(token[2], "pty") {
|
||||
return false
|
||||
}
|
||||
|
||||
if token[3] != `from` && token[3] != `to` {
|
||||
return false
|
||||
}
|
||||
|
||||
if token[4] != "master" {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// getFileNameByHandle use the undocomented ntdll NtQueryObject to get file full name from file handler
|
||||
// since GetFileInformationByHandleEx is not available under windows Vista and still some old fashion
|
||||
// guys are using Windows XP, this is a workaround for those guys, it will also work on system from
|
||||
// Windows vista to 10
|
||||
// see https://stackoverflow.com/a/18792477 for details
|
||||
func getFileNameByHandle(fd uintptr) (string, error) {
|
||||
if procNtQueryObject == nil {
|
||||
return "", errors.New("ntdll.dll: NtQueryObject not supported")
|
||||
}
|
||||
|
||||
var buf [4 + syscall.MAX_PATH]uint16
|
||||
var result int
|
||||
r, _, e := syscall.Syscall6(procNtQueryObject.Addr(), 5,
|
||||
fd, objectNameInfo, uintptr(unsafe.Pointer(&buf)), uintptr(2*len(buf)), uintptr(unsafe.Pointer(&result)), 0)
|
||||
if r != 0 {
|
||||
return "", e
|
||||
}
|
||||
return string(utf16.Decode(buf[4 : 4+buf[0]/2])), nil
|
||||
}
|
||||
|
||||
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
|
||||
// terminal.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
if procGetFileInformationByHandleEx == nil {
|
||||
name, err := getFileNameByHandle(fd)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return isCygwinPipeName(name)
|
||||
}
|
||||
|
||||
// Cygwin/msys's pty is a pipe.
|
||||
ft, _, e := syscall.Syscall(procGetFileType.Addr(), 1, fd, 0, 0)
|
||||
if ft != fileTypePipe || e != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var buf [2 + syscall.MAX_PATH]uint16
|
||||
r, _, e := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(),
|
||||
4, fd, fileNameInfo, uintptr(unsafe.Pointer(&buf)),
|
||||
uintptr(len(buf)*2), 0, 0)
|
||||
if r == 0 || e != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
l := *(*uint32)(unsafe.Pointer(&buf))
|
||||
return isCygwinPipeName(string(utf16.Decode(buf[2 : 2+l/2])))
|
||||
}
|
||||
15
vendor/github.com/ncruces/go-strftime/.gitignore
generated
vendored
Normal file
15
vendor/github.com/ncruces/go-strftime/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
21
vendor/github.com/ncruces/go-strftime/LICENSE
generated
vendored
Normal file
21
vendor/github.com/ncruces/go-strftime/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Nuno Cruces
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
5
vendor/github.com/ncruces/go-strftime/README.md
generated
vendored
Normal file
5
vendor/github.com/ncruces/go-strftime/README.md
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# `strftime`/`strptime` compatible time formatting and parsing for Go
|
||||
|
||||
[](https://pkg.go.dev/github.com/ncruces/go-strftime)
|
||||
[](https://goreportcard.com/report/github.com/ncruces/go-strftime)
|
||||
[](https://raw.githack.com/wiki/ncruces/go-strftime/coverage.html)
|
||||
107
vendor/github.com/ncruces/go-strftime/parser.go
generated
vendored
Normal file
107
vendor/github.com/ncruces/go-strftime/parser.go
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
package strftime
|
||||
|
||||
import "unicode/utf8"
|
||||
|
||||
type parser struct {
|
||||
format func(spec, flag byte) error
|
||||
literal func(byte) error
|
||||
}
|
||||
|
||||
func (p *parser) parse(fmt string) error {
|
||||
const (
|
||||
initial = iota
|
||||
percent
|
||||
flagged
|
||||
modified
|
||||
)
|
||||
|
||||
var flag, modifier byte
|
||||
var err error
|
||||
state := initial
|
||||
start := 0
|
||||
for i, b := range []byte(fmt) {
|
||||
switch state {
|
||||
default:
|
||||
if b == '%' {
|
||||
state = percent
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
err = p.literal(b)
|
||||
|
||||
case percent:
|
||||
if b == '-' || b == ':' {
|
||||
state = flagged
|
||||
flag = b
|
||||
continue
|
||||
}
|
||||
if b == 'E' || b == 'O' {
|
||||
state = modified
|
||||
modifier = b
|
||||
flag = 0
|
||||
continue
|
||||
}
|
||||
err = p.format(b, 0)
|
||||
state = initial
|
||||
|
||||
case flagged:
|
||||
if b == 'E' || b == 'O' {
|
||||
state = modified
|
||||
modifier = b
|
||||
continue
|
||||
}
|
||||
err = p.format(b, flag)
|
||||
state = initial
|
||||
|
||||
case modified:
|
||||
if okModifier(modifier, b) {
|
||||
err = p.format(b, flag)
|
||||
} else {
|
||||
err = p.literals(fmt[start : i+1])
|
||||
}
|
||||
state = initial
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if err, ok := err.(formatError); ok {
|
||||
err.setDirective(fmt, start, i)
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if state != initial {
|
||||
return p.literals(fmt[start:])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *parser) literals(literal string) error {
|
||||
for _, b := range []byte(literal) {
|
||||
if err := p.literal(b); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type literalErr string
|
||||
|
||||
func (e literalErr) Error() string {
|
||||
return "strftime: unsupported literal: " + string(e)
|
||||
}
|
||||
|
||||
type formatError struct {
|
||||
message string
|
||||
directive string
|
||||
}
|
||||
|
||||
func (e formatError) Error() string {
|
||||
return "strftime: unsupported directive: " + e.directive + " " + e.message
|
||||
}
|
||||
|
||||
func (e *formatError) setDirective(str string, i, j int) {
|
||||
_, n := utf8.DecodeRuneInString(str[j:])
|
||||
e.directive = str[i : j+n]
|
||||
}
|
||||
96
vendor/github.com/ncruces/go-strftime/pkg.go
generated
vendored
Normal file
96
vendor/github.com/ncruces/go-strftime/pkg.go
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
Package strftime provides strftime/strptime compatible time formatting and parsing.
|
||||
|
||||
The following specifiers are available:
|
||||
|
||||
Date (Year, Month, Day):
|
||||
%Y - Year with century (can be negative, 4 digits at least)
|
||||
-0001, 0000, 1995, 2009, 14292, etc.
|
||||
%C - year / 100 (round down, 20 in 2009)
|
||||
%y - year % 100 (00..99)
|
||||
|
||||
%m - Month of the year, zero-padded (01..12)
|
||||
%-m no-padded (1..12)
|
||||
%B - Full month name (January)
|
||||
%b - Abbreviated month name (Jan)
|
||||
%h - Equivalent to %b
|
||||
|
||||
%d - Day of the month, zero-padded (01..31)
|
||||
%-d no-padded (1..31)
|
||||
%e - Day of the month, blank-padded ( 1..31)
|
||||
|
||||
%j - Day of the year (001..366)
|
||||
%-j no-padded (1..366)
|
||||
|
||||
Time (Hour, Minute, Second, Subsecond):
|
||||
%H - Hour of the day, 24-hour clock, zero-padded (00..23)
|
||||
%-H no-padded (0..23)
|
||||
%k - Hour of the day, 24-hour clock, blank-padded ( 0..23)
|
||||
%I - Hour of the day, 12-hour clock, zero-padded (01..12)
|
||||
%-I no-padded (1..12)
|
||||
%l - Hour of the day, 12-hour clock, blank-padded ( 1..12)
|
||||
%P - Meridian indicator, lowercase (am or pm)
|
||||
%p - Meridian indicator, uppercase (AM or PM)
|
||||
|
||||
%M - Minute of the hour (00..59)
|
||||
%-M no-padded (0..59)
|
||||
|
||||
%S - Second of the minute (00..60)
|
||||
%-S no-padded (0..60)
|
||||
|
||||
%L - Millisecond of the second (000..999)
|
||||
%f - Microsecond of the second (000000..999999)
|
||||
%N - Nanosecond of the second (000000000..999999999)
|
||||
|
||||
Time zone:
|
||||
%z - Time zone as hour and minute offset from UTC (e.g. +0900)
|
||||
%:z - hour and minute offset from UTC with a colon (e.g. +09:00)
|
||||
%Z - Time zone abbreviation (e.g. MST)
|
||||
|
||||
Weekday:
|
||||
%A - Full weekday name (Sunday)
|
||||
%a - Abbreviated weekday name (Sun)
|
||||
%u - Day of the week (Monday is 1, 1..7)
|
||||
%w - Day of the week (Sunday is 0, 0..6)
|
||||
|
||||
ISO 8601 week-based year and week number:
|
||||
Week 1 of YYYY starts with a Monday and includes YYYY-01-04.
|
||||
The days in the year before the first week are in the last week of
|
||||
the previous year.
|
||||
%G - Week-based year
|
||||
%g - Last 2 digits of the week-based year (00..99)
|
||||
%V - Week number of the week-based year (01..53)
|
||||
%-V no-padded (1..53)
|
||||
|
||||
Week number:
|
||||
Week 1 of YYYY starts with a Sunday or Monday (according to %U or %W).
|
||||
The days in the year before the first week are in week 0.
|
||||
%U - Week number of the year. The week starts with Sunday. (00..53)
|
||||
%-U no-padded (0..53)
|
||||
%W - Week number of the year. The week starts with Monday. (00..53)
|
||||
%-W no-padded (0..53)
|
||||
|
||||
Seconds since the Unix Epoch:
|
||||
%s - Number of seconds since 1970-01-01 00:00:00 UTC.
|
||||
%Q - Number of milliseconds since 1970-01-01 00:00:00 UTC.
|
||||
|
||||
Literal string:
|
||||
%n - Newline character (\n)
|
||||
%t - Tab character (\t)
|
||||
%% - Literal % character
|
||||
|
||||
Combination:
|
||||
%c - date and time (%a %b %e %T %Y)
|
||||
%D - Date (%m/%d/%y)
|
||||
%F - ISO 8601 date format (%Y-%m-%d)
|
||||
%v - VMS date (%e-%b-%Y)
|
||||
%x - Same as %D
|
||||
%X - Same as %T
|
||||
%r - 12-hour time (%I:%M:%S %p)
|
||||
%R - 24-hour time (%H:%M)
|
||||
%T - 24-hour time (%H:%M:%S)
|
||||
%+ - date(1) (%a %b %e %H:%M:%S %Z %Y)
|
||||
|
||||
The modifiers ``E'' and ``O'' are ignored.
|
||||
*/
|
||||
package strftime
|
||||
241
vendor/github.com/ncruces/go-strftime/specifiers.go
generated
vendored
Normal file
241
vendor/github.com/ncruces/go-strftime/specifiers.go
generated
vendored
Normal file
@@ -0,0 +1,241 @@
|
||||
package strftime
|
||||
|
||||
import "strings"
|
||||
|
||||
// https://strftime.org/
|
||||
func goLayout(spec, flag byte, parsing bool) string {
|
||||
switch spec {
|
||||
default:
|
||||
return ""
|
||||
|
||||
case 'B':
|
||||
return "January"
|
||||
case 'b', 'h':
|
||||
return "Jan"
|
||||
case 'm':
|
||||
if flag == '-' || parsing {
|
||||
return "1"
|
||||
}
|
||||
return "01"
|
||||
case 'A':
|
||||
return "Monday"
|
||||
case 'a':
|
||||
return "Mon"
|
||||
case 'e':
|
||||
return "_2"
|
||||
case 'd':
|
||||
if flag == '-' || parsing {
|
||||
return "2"
|
||||
}
|
||||
return "02"
|
||||
case 'j':
|
||||
if flag == '-' {
|
||||
if parsing {
|
||||
return "__2"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
return "002"
|
||||
case 'I':
|
||||
if flag == '-' || parsing {
|
||||
return "3"
|
||||
}
|
||||
return "03"
|
||||
case 'H':
|
||||
if flag == '-' && !parsing {
|
||||
return ""
|
||||
}
|
||||
return "15"
|
||||
case 'M':
|
||||
if flag == '-' || parsing {
|
||||
return "4"
|
||||
}
|
||||
return "04"
|
||||
case 'S':
|
||||
if flag == '-' || parsing {
|
||||
return "5"
|
||||
}
|
||||
return "05"
|
||||
case 'y':
|
||||
return "06"
|
||||
case 'Y':
|
||||
return "2006"
|
||||
case 'p':
|
||||
return "PM"
|
||||
case 'P':
|
||||
return "pm"
|
||||
case 'Z':
|
||||
return "MST"
|
||||
case 'z':
|
||||
if flag == ':' {
|
||||
if parsing {
|
||||
return "Z07:00"
|
||||
}
|
||||
return "-07:00"
|
||||
}
|
||||
if parsing {
|
||||
return "Z0700"
|
||||
}
|
||||
return "-0700"
|
||||
|
||||
case '+':
|
||||
if parsing {
|
||||
return "Mon Jan _2 15:4:5 MST 2006"
|
||||
}
|
||||
return "Mon Jan _2 15:04:05 MST 2006"
|
||||
case 'c':
|
||||
if parsing {
|
||||
return "Mon Jan _2 15:4:5 2006"
|
||||
}
|
||||
return "Mon Jan _2 15:04:05 2006"
|
||||
case 'v':
|
||||
return "_2-Jan-2006"
|
||||
case 'F':
|
||||
if parsing {
|
||||
return "2006-1-2"
|
||||
}
|
||||
return "2006-01-02"
|
||||
case 'D', 'x':
|
||||
if parsing {
|
||||
return "1/2/06"
|
||||
}
|
||||
return "01/02/06"
|
||||
case 'r':
|
||||
if parsing {
|
||||
return "3:4:5 PM"
|
||||
}
|
||||
return "03:04:05 PM"
|
||||
case 'T', 'X':
|
||||
if parsing {
|
||||
return "15:4:5"
|
||||
}
|
||||
return "15:04:05"
|
||||
case 'R':
|
||||
if parsing {
|
||||
return "15:4"
|
||||
}
|
||||
return "15:04"
|
||||
|
||||
case '%':
|
||||
return "%"
|
||||
case 't':
|
||||
return "\t"
|
||||
case 'n':
|
||||
return "\n"
|
||||
}
|
||||
}
|
||||
|
||||
// https://nsdateformatter.com/
|
||||
func uts35Pattern(spec, flag byte) string {
|
||||
switch spec {
|
||||
default:
|
||||
return ""
|
||||
|
||||
case 'B':
|
||||
return "MMMM"
|
||||
case 'b', 'h':
|
||||
return "MMM"
|
||||
case 'm':
|
||||
if flag == '-' {
|
||||
return "M"
|
||||
}
|
||||
return "MM"
|
||||
case 'A':
|
||||
return "EEEE"
|
||||
case 'a':
|
||||
return "E"
|
||||
case 'd':
|
||||
if flag == '-' {
|
||||
return "d"
|
||||
}
|
||||
return "dd"
|
||||
case 'j':
|
||||
if flag == '-' {
|
||||
return "D"
|
||||
}
|
||||
return "DDD"
|
||||
case 'I':
|
||||
if flag == '-' {
|
||||
return "h"
|
||||
}
|
||||
return "hh"
|
||||
case 'H':
|
||||
if flag == '-' {
|
||||
return "H"
|
||||
}
|
||||
return "HH"
|
||||
case 'M':
|
||||
if flag == '-' {
|
||||
return "m"
|
||||
}
|
||||
return "mm"
|
||||
case 'S':
|
||||
if flag == '-' {
|
||||
return "s"
|
||||
}
|
||||
return "ss"
|
||||
case 'y':
|
||||
return "yy"
|
||||
case 'Y':
|
||||
return "yyyy"
|
||||
case 'g':
|
||||
return "YY"
|
||||
case 'G':
|
||||
return "YYYY"
|
||||
case 'V':
|
||||
if flag == '-' {
|
||||
return "w"
|
||||
}
|
||||
return "ww"
|
||||
case 'p':
|
||||
return "a"
|
||||
case 'Z':
|
||||
return "zzz"
|
||||
case 'z':
|
||||
if flag == ':' {
|
||||
return "xxx"
|
||||
}
|
||||
return "xx"
|
||||
case 'L':
|
||||
return "SSS"
|
||||
case 'f':
|
||||
return "SSSSSS"
|
||||
case 'N':
|
||||
return "SSSSSSSSS"
|
||||
|
||||
case '+':
|
||||
return "E MMM d HH:mm:ss zzz yyyy"
|
||||
case 'c':
|
||||
return "E MMM d HH:mm:ss yyyy"
|
||||
case 'v':
|
||||
return "d-MMM-yyyy"
|
||||
case 'F':
|
||||
return "yyyy-MM-dd"
|
||||
case 'D', 'x':
|
||||
return "MM/dd/yy"
|
||||
case 'r':
|
||||
return "hh:mm:ss a"
|
||||
case 'T', 'X':
|
||||
return "HH:mm:ss"
|
||||
case 'R':
|
||||
return "HH:mm"
|
||||
|
||||
case '%':
|
||||
return "%"
|
||||
case 't':
|
||||
return "\t"
|
||||
case 'n':
|
||||
return "\n"
|
||||
}
|
||||
}
|
||||
|
||||
// http://man.he.net/man3/strftime
|
||||
func okModifier(mod, spec byte) bool {
|
||||
if mod == 'E' {
|
||||
return strings.Contains("cCxXyY", string(spec))
|
||||
}
|
||||
if mod == 'O' {
|
||||
return strings.Contains("deHImMSuUVwWy", string(spec))
|
||||
}
|
||||
return false
|
||||
}
|
||||
324
vendor/github.com/ncruces/go-strftime/strftime.go
generated
vendored
Normal file
324
vendor/github.com/ncruces/go-strftime/strftime.go
generated
vendored
Normal file
@@ -0,0 +1,324 @@
|
||||
package strftime
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Format returns a textual representation of the time value
|
||||
// formatted according to the strftime format specification.
|
||||
func Format(fmt string, t time.Time) string {
|
||||
buf := buffer(fmt)
|
||||
return string(AppendFormat(buf, fmt, t))
|
||||
}
|
||||
|
||||
// AppendFormat is like Format, but appends the textual representation
|
||||
// to dst and returns the extended buffer.
|
||||
func AppendFormat(dst []byte, fmt string, t time.Time) []byte {
|
||||
var parser parser
|
||||
|
||||
parser.literal = func(b byte) error {
|
||||
dst = append(dst, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
parser.format = func(spec, flag byte) error {
|
||||
switch spec {
|
||||
case 'A':
|
||||
dst = append(dst, t.Weekday().String()...)
|
||||
return nil
|
||||
case 'a':
|
||||
dst = append(dst, t.Weekday().String()[:3]...)
|
||||
return nil
|
||||
case 'B':
|
||||
dst = append(dst, t.Month().String()...)
|
||||
return nil
|
||||
case 'b', 'h':
|
||||
dst = append(dst, t.Month().String()[:3]...)
|
||||
return nil
|
||||
case 'm':
|
||||
dst = appendInt2(dst, int(t.Month()), flag)
|
||||
return nil
|
||||
case 'd':
|
||||
dst = appendInt2(dst, int(t.Day()), flag)
|
||||
return nil
|
||||
case 'e':
|
||||
dst = appendInt2(dst, int(t.Day()), ' ')
|
||||
return nil
|
||||
case 'I':
|
||||
dst = append12Hour(dst, t, flag)
|
||||
return nil
|
||||
case 'l':
|
||||
dst = append12Hour(dst, t, ' ')
|
||||
return nil
|
||||
case 'H':
|
||||
dst = appendInt2(dst, t.Hour(), flag)
|
||||
return nil
|
||||
case 'k':
|
||||
dst = appendInt2(dst, t.Hour(), ' ')
|
||||
return nil
|
||||
case 'M':
|
||||
dst = appendInt2(dst, t.Minute(), flag)
|
||||
return nil
|
||||
case 'S':
|
||||
dst = appendInt2(dst, t.Second(), flag)
|
||||
return nil
|
||||
case 'L':
|
||||
dst = append(dst, t.Format(".000")[1:]...)
|
||||
return nil
|
||||
case 'f':
|
||||
dst = append(dst, t.Format(".000000")[1:]...)
|
||||
return nil
|
||||
case 'N':
|
||||
dst = append(dst, t.Format(".000000000")[1:]...)
|
||||
return nil
|
||||
case 'y':
|
||||
dst = t.AppendFormat(dst, "06")
|
||||
return nil
|
||||
case 'Y':
|
||||
dst = t.AppendFormat(dst, "2006")
|
||||
return nil
|
||||
case 'C':
|
||||
dst = t.AppendFormat(dst, "2006")
|
||||
dst = dst[:len(dst)-2]
|
||||
return nil
|
||||
case 'U':
|
||||
dst = appendWeekNumber(dst, t, flag, true)
|
||||
return nil
|
||||
case 'W':
|
||||
dst = appendWeekNumber(dst, t, flag, false)
|
||||
return nil
|
||||
case 'V':
|
||||
_, w := t.ISOWeek()
|
||||
dst = appendInt2(dst, w, flag)
|
||||
return nil
|
||||
case 'g':
|
||||
y, _ := t.ISOWeek()
|
||||
dst = year(y).AppendFormat(dst, "06")
|
||||
return nil
|
||||
case 'G':
|
||||
y, _ := t.ISOWeek()
|
||||
dst = year(y).AppendFormat(dst, "2006")
|
||||
return nil
|
||||
case 's':
|
||||
dst = strconv.AppendInt(dst, t.Unix(), 10)
|
||||
return nil
|
||||
case 'Q':
|
||||
dst = strconv.AppendInt(dst, t.UnixMilli(), 10)
|
||||
return nil
|
||||
case 'w':
|
||||
w := t.Weekday()
|
||||
dst = appendInt1(dst, int(w))
|
||||
return nil
|
||||
case 'u':
|
||||
if w := t.Weekday(); w == 0 {
|
||||
dst = append(dst, '7')
|
||||
} else {
|
||||
dst = appendInt1(dst, int(w))
|
||||
}
|
||||
return nil
|
||||
case 'j':
|
||||
if flag == '-' {
|
||||
dst = strconv.AppendInt(dst, int64(t.YearDay()), 10)
|
||||
} else {
|
||||
dst = t.AppendFormat(dst, "002")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if layout := goLayout(spec, flag, false); layout != "" {
|
||||
dst = t.AppendFormat(dst, layout)
|
||||
return nil
|
||||
}
|
||||
|
||||
dst = append(dst, '%')
|
||||
if flag != 0 {
|
||||
dst = append(dst, flag)
|
||||
}
|
||||
dst = append(dst, spec)
|
||||
return nil
|
||||
}
|
||||
|
||||
parser.parse(fmt)
|
||||
return dst
|
||||
}
|
||||
|
||||
// Parse converts a textual representation of time to the time value it represents
|
||||
// according to the strptime format specification.
|
||||
func Parse(fmt, value string) (time.Time, error) {
|
||||
pattern, err := layout(fmt, true)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return time.Parse(pattern, value)
|
||||
}
|
||||
|
||||
// Layout converts a strftime format specification
|
||||
// to a Go time pattern specification.
|
||||
func Layout(fmt string) (string, error) {
|
||||
return layout(fmt, false)
|
||||
}
|
||||
|
||||
func layout(fmt string, parsing bool) (string, error) {
|
||||
dst := buffer(fmt)
|
||||
var parser parser
|
||||
|
||||
parser.literal = func(b byte) error {
|
||||
if '0' <= b && b <= '9' {
|
||||
return literalErr(b)
|
||||
}
|
||||
dst = append(dst, b)
|
||||
if b == 'M' || b == 'T' || b == 'm' || b == 'n' {
|
||||
switch {
|
||||
case bytes.HasSuffix(dst, []byte("Jan")):
|
||||
return literalErr("Jan")
|
||||
case bytes.HasSuffix(dst, []byte("Mon")):
|
||||
return literalErr("Mon")
|
||||
case bytes.HasSuffix(dst, []byte("MST")):
|
||||
return literalErr("MST")
|
||||
case bytes.HasSuffix(dst, []byte("PM")):
|
||||
return literalErr("PM")
|
||||
case bytes.HasSuffix(dst, []byte("pm")):
|
||||
return literalErr("pm")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
parser.format = func(spec, flag byte) error {
|
||||
if layout := goLayout(spec, flag, parsing); layout != "" {
|
||||
dst = append(dst, layout...)
|
||||
return nil
|
||||
}
|
||||
|
||||
switch spec {
|
||||
default:
|
||||
return formatError{}
|
||||
|
||||
case 'L', 'f', 'N':
|
||||
if bytes.HasSuffix(dst, []byte(".")) || bytes.HasSuffix(dst, []byte(",")) {
|
||||
switch spec {
|
||||
default:
|
||||
dst = append(dst, "000"...)
|
||||
case 'f':
|
||||
dst = append(dst, "000000"...)
|
||||
case 'N':
|
||||
dst = append(dst, "000000000"...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return formatError{message: "must follow '.' or ','"}
|
||||
}
|
||||
}
|
||||
|
||||
if err := parser.parse(fmt); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(dst), nil
|
||||
}
|
||||
|
||||
// UTS35 converts a strftime format specification
|
||||
// to a Unicode Technical Standard #35 Date Format Pattern.
|
||||
func UTS35(fmt string) (string, error) {
|
||||
const quote = '\''
|
||||
var quoted bool
|
||||
dst := buffer(fmt)
|
||||
|
||||
var parser parser
|
||||
|
||||
parser.literal = func(b byte) error {
|
||||
if b == quote {
|
||||
dst = append(dst, quote, quote)
|
||||
return nil
|
||||
}
|
||||
if !quoted && ('a' <= b && b <= 'z' || 'A' <= b && b <= 'Z') {
|
||||
dst = append(dst, quote)
|
||||
quoted = true
|
||||
}
|
||||
dst = append(dst, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
parser.format = func(spec, flag byte) error {
|
||||
if quoted {
|
||||
dst = append(dst, quote)
|
||||
quoted = false
|
||||
}
|
||||
if pattern := uts35Pattern(spec, flag); pattern != "" {
|
||||
dst = append(dst, pattern...)
|
||||
return nil
|
||||
}
|
||||
return formatError{}
|
||||
}
|
||||
|
||||
if err := parser.parse(fmt); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if quoted {
|
||||
dst = append(dst, quote)
|
||||
}
|
||||
return string(dst), nil
|
||||
}
|
||||
|
||||
func buffer(format string) (buf []byte) {
|
||||
const bufSize = 64
|
||||
max := len(format) + 10
|
||||
if max < bufSize {
|
||||
var b [bufSize]byte
|
||||
buf = b[:0]
|
||||
} else {
|
||||
buf = make([]byte, 0, max)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func year(y int) time.Time {
|
||||
return time.Date(y, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
}
|
||||
|
||||
func appendWeekNumber(dst []byte, t time.Time, flag byte, sunday bool) []byte {
|
||||
offset := int(t.Weekday())
|
||||
if sunday {
|
||||
offset = 6 - offset
|
||||
} else if offset != 0 {
|
||||
offset = 7 - offset
|
||||
}
|
||||
return appendInt2(dst, (t.YearDay()+offset)/7, flag)
|
||||
}
|
||||
|
||||
func append12Hour(dst []byte, t time.Time, flag byte) []byte {
|
||||
h := t.Hour()
|
||||
if h == 0 {
|
||||
h = 12
|
||||
} else if h > 12 {
|
||||
h -= 12
|
||||
}
|
||||
return appendInt2(dst, h, flag)
|
||||
}
|
||||
|
||||
func appendInt1(dst []byte, i int) []byte {
|
||||
return append(dst, byte('0'+i))
|
||||
}
|
||||
|
||||
func appendInt2(dst []byte, i int, flag byte) []byte {
|
||||
if flag == 0 || i >= 10 {
|
||||
return append(dst, smallsString[i*2:i*2+2]...)
|
||||
}
|
||||
if flag == ' ' {
|
||||
dst = append(dst, flag)
|
||||
}
|
||||
return appendInt1(dst, i)
|
||||
}
|
||||
|
||||
const smallsString = "" +
|
||||
"00010203040506070809" +
|
||||
"10111213141516171819" +
|
||||
"20212223242526272829" +
|
||||
"30313233343536373839" +
|
||||
"40414243444546474849" +
|
||||
"50515253545556575859" +
|
||||
"60616263646566676869" +
|
||||
"70717273747576777879" +
|
||||
"80818283848586878889" +
|
||||
"90919293949596979899"
|
||||
27
vendor/github.com/remyoudompheng/bigfft/LICENSE
generated
vendored
Normal file
27
vendor/github.com/remyoudompheng/bigfft/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
54
vendor/github.com/remyoudompheng/bigfft/README
generated
vendored
Normal file
54
vendor/github.com/remyoudompheng/bigfft/README
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
This library is a toy proof-of-concept implementation of the
|
||||
well-known Schonhage-Strassen method for multiplying integers.
|
||||
It is not expected to have a real life usecase outside number
|
||||
theory computations, nor is it expected to be used in any production
|
||||
system.
|
||||
|
||||
If you are using it in your project, you may want to carefully
|
||||
examine the actual requirement or problem you are trying to solve.
|
||||
|
||||
# Comparison with the standard library and GMP
|
||||
|
||||
Benchmarking math/big vs. bigfft
|
||||
|
||||
Number size old ns/op new ns/op delta
|
||||
1kb 1599 1640 +2.56%
|
||||
10kb 61533 62170 +1.04%
|
||||
50kb 833693 831051 -0.32%
|
||||
100kb 2567995 2693864 +4.90%
|
||||
1Mb 105237800 28446400 -72.97%
|
||||
5Mb 1272947000 168554600 -86.76%
|
||||
10Mb 3834354000 405120200 -89.43%
|
||||
20Mb 11514488000 845081600 -92.66%
|
||||
50Mb 49199945000 2893950000 -94.12%
|
||||
100Mb 147599836000 5921594000 -95.99%
|
||||
|
||||
Benchmarking GMP vs bigfft
|
||||
|
||||
Number size GMP ns/op Go ns/op delta
|
||||
1kb 536 1500 +179.85%
|
||||
10kb 26669 50777 +90.40%
|
||||
50kb 252270 658534 +161.04%
|
||||
100kb 686813 2127534 +209.77%
|
||||
1Mb 12100000 22391830 +85.06%
|
||||
5Mb 111731843 133550600 +19.53%
|
||||
10Mb 212314000 318595800 +50.06%
|
||||
20Mb 490196000 671512800 +36.99%
|
||||
50Mb 1280000000 2451476000 +91.52%
|
||||
100Mb 2673000000 5228991000 +95.62%
|
||||
|
||||
Benchmarks were run on a Core 2 Quad Q8200 (2.33GHz).
|
||||
FFT is enabled when input numbers are over 200kbits.
|
||||
|
||||
Scanning large decimal number from strings.
|
||||
(math/big [n^2 complexity] vs bigfft [n^1.6 complexity], Core i5-4590)
|
||||
|
||||
Digits old ns/op new ns/op delta
|
||||
1e3 9995 10876 +8.81%
|
||||
1e4 175356 243806 +39.03%
|
||||
1e5 9427422 6780545 -28.08%
|
||||
1e6 1776707489 144867502 -91.85%
|
||||
2e6 6865499995 346540778 -94.95%
|
||||
5e6 42641034189 1069878799 -97.49%
|
||||
10e6 151975273589 2693328580 -98.23%
|
||||
|
||||
33
vendor/github.com/remyoudompheng/bigfft/arith_decl.go
generated
vendored
Normal file
33
vendor/github.com/remyoudompheng/bigfft/arith_decl.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bigfft
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
type Word = big.Word
|
||||
|
||||
//go:linkname addVV math/big.addVV
|
||||
func addVV(z, x, y []Word) (c Word)
|
||||
|
||||
//go:linkname subVV math/big.subVV
|
||||
func subVV(z, x, y []Word) (c Word)
|
||||
|
||||
//go:linkname addVW math/big.addVW
|
||||
func addVW(z, x []Word, y Word) (c Word)
|
||||
|
||||
//go:linkname subVW math/big.subVW
|
||||
func subVW(z, x []Word, y Word) (c Word)
|
||||
|
||||
//go:linkname shlVU math/big.shlVU
|
||||
func shlVU(z, x []Word, s uint) (c Word)
|
||||
|
||||
//go:linkname mulAddVWW math/big.mulAddVWW
|
||||
func mulAddVWW(z, x []Word, y, r Word) (c Word)
|
||||
|
||||
//go:linkname addMulVVW math/big.addMulVVW
|
||||
func addMulVVW(z, x []Word, y Word) (c Word)
|
||||
216
vendor/github.com/remyoudompheng/bigfft/fermat.go
generated
vendored
Normal file
216
vendor/github.com/remyoudompheng/bigfft/fermat.go
generated
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
package bigfft
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// Arithmetic modulo 2^n+1.
|
||||
|
||||
// A fermat of length w+1 represents a number modulo 2^(w*_W) + 1. The last
|
||||
// word is zero or one. A number has at most two representatives satisfying the
|
||||
// 0-1 last word constraint.
|
||||
type fermat nat
|
||||
|
||||
func (n fermat) String() string { return nat(n).String() }
|
||||
|
||||
func (z fermat) norm() {
|
||||
n := len(z) - 1
|
||||
c := z[n]
|
||||
if c == 0 {
|
||||
return
|
||||
}
|
||||
if z[0] >= c {
|
||||
z[n] = 0
|
||||
z[0] -= c
|
||||
return
|
||||
}
|
||||
// z[0] < z[n].
|
||||
subVW(z, z, c) // Substract c
|
||||
if c > 1 {
|
||||
z[n] -= c - 1
|
||||
c = 1
|
||||
}
|
||||
// Add back c.
|
||||
if z[n] == 1 {
|
||||
z[n] = 0
|
||||
return
|
||||
} else {
|
||||
addVW(z, z, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Shift computes (x << k) mod (2^n+1).
|
||||
func (z fermat) Shift(x fermat, k int) {
|
||||
if len(z) != len(x) {
|
||||
panic("len(z) != len(x) in Shift")
|
||||
}
|
||||
n := len(x) - 1
|
||||
// Shift by n*_W is taking the opposite.
|
||||
k %= 2 * n * _W
|
||||
if k < 0 {
|
||||
k += 2 * n * _W
|
||||
}
|
||||
neg := false
|
||||
if k >= n*_W {
|
||||
k -= n * _W
|
||||
neg = true
|
||||
}
|
||||
|
||||
kw, kb := k/_W, k%_W
|
||||
|
||||
z[n] = 1 // Add (-1)
|
||||
if !neg {
|
||||
for i := 0; i < kw; i++ {
|
||||
z[i] = 0
|
||||
}
|
||||
// Shift left by kw words.
|
||||
// x = a·2^(n-k) + b
|
||||
// x<<k = (b<<k) - a
|
||||
copy(z[kw:], x[:n-kw])
|
||||
b := subVV(z[:kw+1], z[:kw+1], x[n-kw:])
|
||||
if z[kw+1] > 0 {
|
||||
z[kw+1] -= b
|
||||
} else {
|
||||
subVW(z[kw+1:], z[kw+1:], b)
|
||||
}
|
||||
} else {
|
||||
for i := kw + 1; i < n; i++ {
|
||||
z[i] = 0
|
||||
}
|
||||
// Shift left and negate, by kw words.
|
||||
copy(z[:kw+1], x[n-kw:n+1]) // z_low = x_high
|
||||
b := subVV(z[kw:n], z[kw:n], x[:n-kw]) // z_high -= x_low
|
||||
z[n] -= b
|
||||
}
|
||||
// Add back 1.
|
||||
if z[n] > 0 {
|
||||
z[n]--
|
||||
} else if z[0] < ^big.Word(0) {
|
||||
z[0]++
|
||||
} else {
|
||||
addVW(z, z, 1)
|
||||
}
|
||||
// Shift left by kb bits
|
||||
shlVU(z, z, uint(kb))
|
||||
z.norm()
|
||||
}
|
||||
|
||||
// ShiftHalf shifts x by k/2 bits the left. Shifting by 1/2 bit
|
||||
// is multiplication by sqrt(2) mod 2^n+1 which is 2^(3n/4) - 2^(n/4).
|
||||
// A temporary buffer must be provided in tmp.
|
||||
func (z fermat) ShiftHalf(x fermat, k int, tmp fermat) {
|
||||
n := len(z) - 1
|
||||
if k%2 == 0 {
|
||||
z.Shift(x, k/2)
|
||||
return
|
||||
}
|
||||
u := (k - 1) / 2
|
||||
a := u + (3*_W/4)*n
|
||||
b := u + (_W/4)*n
|
||||
z.Shift(x, a)
|
||||
tmp.Shift(x, b)
|
||||
z.Sub(z, tmp)
|
||||
}
|
||||
|
||||
// Add computes addition mod 2^n+1.
|
||||
func (z fermat) Add(x, y fermat) fermat {
|
||||
if len(z) != len(x) {
|
||||
panic("Add: len(z) != len(x)")
|
||||
}
|
||||
addVV(z, x, y) // there cannot be a carry here.
|
||||
z.norm()
|
||||
return z
|
||||
}
|
||||
|
||||
// Sub computes substraction mod 2^n+1.
|
||||
func (z fermat) Sub(x, y fermat) fermat {
|
||||
if len(z) != len(x) {
|
||||
panic("Add: len(z) != len(x)")
|
||||
}
|
||||
n := len(y) - 1
|
||||
b := subVV(z[:n], x[:n], y[:n])
|
||||
b += y[n]
|
||||
// If b > 0, we need to subtract b<<n, which is the same as adding b.
|
||||
z[n] = x[n]
|
||||
if z[0] <= ^big.Word(0)-b {
|
||||
z[0] += b
|
||||
} else {
|
||||
addVW(z, z, b)
|
||||
}
|
||||
z.norm()
|
||||
return z
|
||||
}
|
||||
|
||||
func (z fermat) Mul(x, y fermat) fermat {
|
||||
if len(x) != len(y) {
|
||||
panic("Mul: len(x) != len(y)")
|
||||
}
|
||||
n := len(x) - 1
|
||||
if n < 30 {
|
||||
z = z[:2*n+2]
|
||||
basicMul(z, x, y)
|
||||
z = z[:2*n+1]
|
||||
} else {
|
||||
var xi, yi, zi big.Int
|
||||
xi.SetBits(x)
|
||||
yi.SetBits(y)
|
||||
zi.SetBits(z)
|
||||
zb := zi.Mul(&xi, &yi).Bits()
|
||||
if len(zb) <= n {
|
||||
// Short product.
|
||||
copy(z, zb)
|
||||
for i := len(zb); i < len(z); i++ {
|
||||
z[i] = 0
|
||||
}
|
||||
return z
|
||||
}
|
||||
z = zb
|
||||
}
|
||||
// len(z) is at most 2n+1.
|
||||
if len(z) > 2*n+1 {
|
||||
panic("len(z) > 2n+1")
|
||||
}
|
||||
// We now have
|
||||
// z = z[:n] + 1<<(n*W) * z[n:2n+1]
|
||||
// which normalizes to:
|
||||
// z = z[:n] - z[n:2n] + z[2n]
|
||||
c1 := big.Word(0)
|
||||
if len(z) > 2*n {
|
||||
c1 = addVW(z[:n], z[:n], z[2*n])
|
||||
}
|
||||
c2 := big.Word(0)
|
||||
if len(z) >= 2*n {
|
||||
c2 = subVV(z[:n], z[:n], z[n:2*n])
|
||||
} else {
|
||||
m := len(z) - n
|
||||
c2 = subVV(z[:m], z[:m], z[n:])
|
||||
c2 = subVW(z[m:n], z[m:n], c2)
|
||||
}
|
||||
// Restore carries.
|
||||
// Substracting z[n] -= c2 is the same
|
||||
// as z[0] += c2
|
||||
z = z[:n+1]
|
||||
z[n] = c1
|
||||
c := addVW(z, z, c2)
|
||||
if c != 0 {
|
||||
panic("impossible")
|
||||
}
|
||||
z.norm()
|
||||
return z
|
||||
}
|
||||
|
||||
// copied from math/big
|
||||
//
|
||||
// basicMul multiplies x and y and leaves the result in z.
|
||||
// The (non-normalized) result is placed in z[0 : len(x) + len(y)].
|
||||
func basicMul(z, x, y fermat) {
|
||||
// initialize z
|
||||
for i := 0; i < len(z); i++ {
|
||||
z[i] = 0
|
||||
}
|
||||
for i, d := range y {
|
||||
if d != 0 {
|
||||
z[len(x)+i] = addMulVVW(z[i:i+len(x)], x, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
370
vendor/github.com/remyoudompheng/bigfft/fft.go
generated
vendored
Normal file
370
vendor/github.com/remyoudompheng/bigfft/fft.go
generated
vendored
Normal file
@@ -0,0 +1,370 @@
|
||||
// Package bigfft implements multiplication of big.Int using FFT.
|
||||
//
|
||||
// The implementation is based on the Schönhage-Strassen method
|
||||
// using integer FFT modulo 2^n+1.
|
||||
package bigfft
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const _W = int(unsafe.Sizeof(big.Word(0)) * 8)
|
||||
|
||||
type nat []big.Word
|
||||
|
||||
func (n nat) String() string {
|
||||
v := new(big.Int)
|
||||
v.SetBits(n)
|
||||
return v.String()
|
||||
}
|
||||
|
||||
// fftThreshold is the size (in words) above which FFT is used over
|
||||
// Karatsuba from math/big.
|
||||
//
|
||||
// TestCalibrate seems to indicate a threshold of 60kbits on 32-bit
|
||||
// arches and 110kbits on 64-bit arches.
|
||||
var fftThreshold = 1800
|
||||
|
||||
// Mul computes the product x*y and returns z.
|
||||
// It can be used instead of the Mul method of
|
||||
// *big.Int from math/big package.
|
||||
func Mul(x, y *big.Int) *big.Int {
|
||||
xwords := len(x.Bits())
|
||||
ywords := len(y.Bits())
|
||||
if xwords > fftThreshold && ywords > fftThreshold {
|
||||
return mulFFT(x, y)
|
||||
}
|
||||
return new(big.Int).Mul(x, y)
|
||||
}
|
||||
|
||||
func mulFFT(x, y *big.Int) *big.Int {
|
||||
var xb, yb nat = x.Bits(), y.Bits()
|
||||
zb := fftmul(xb, yb)
|
||||
z := new(big.Int)
|
||||
z.SetBits(zb)
|
||||
if x.Sign()*y.Sign() < 0 {
|
||||
z.Neg(z)
|
||||
}
|
||||
return z
|
||||
}
|
||||
|
||||
// A FFT size of K=1<<k is adequate when K is about 2*sqrt(N) where
|
||||
// N = x.Bitlen() + y.Bitlen().
|
||||
|
||||
func fftmul(x, y nat) nat {
|
||||
k, m := fftSize(x, y)
|
||||
xp := polyFromNat(x, k, m)
|
||||
yp := polyFromNat(y, k, m)
|
||||
rp := xp.Mul(&yp)
|
||||
return rp.Int()
|
||||
}
|
||||
|
||||
// fftSizeThreshold[i] is the maximal size (in bits) where we should use
|
||||
// fft size i.
|
||||
var fftSizeThreshold = [...]int64{0, 0, 0,
|
||||
4 << 10, 8 << 10, 16 << 10, // 5
|
||||
32 << 10, 64 << 10, 1 << 18, 1 << 20, 3 << 20, // 10
|
||||
8 << 20, 30 << 20, 100 << 20, 300 << 20, 600 << 20,
|
||||
}
|
||||
|
||||
// returns the FFT length k, m the number of words per chunk
|
||||
// such that m << k is larger than the number of words
|
||||
// in x*y.
|
||||
func fftSize(x, y nat) (k uint, m int) {
|
||||
words := len(x) + len(y)
|
||||
bits := int64(words) * int64(_W)
|
||||
k = uint(len(fftSizeThreshold))
|
||||
for i := range fftSizeThreshold {
|
||||
if fftSizeThreshold[i] > bits {
|
||||
k = uint(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
// The 1<<k chunks of m words must have N bits so that
|
||||
// 2^N-1 is larger than x*y. That is, m<<k > words
|
||||
m = words>>k + 1
|
||||
return
|
||||
}
|
||||
|
||||
// valueSize returns the length (in words) to use for polynomial
|
||||
// coefficients, to compute a correct product of polynomials P*Q
|
||||
// where deg(P*Q) < K (== 1<<k) and where coefficients of P and Q are
|
||||
// less than b^m (== 1 << (m*_W)).
|
||||
// The chosen length (in bits) must be a multiple of 1 << (k-extra).
|
||||
func valueSize(k uint, m int, extra uint) int {
|
||||
// The coefficients of P*Q are less than b^(2m)*K
|
||||
// so we need W * valueSize >= 2*m*W+K
|
||||
n := 2*m*_W + int(k) // necessary bits
|
||||
K := 1 << (k - extra)
|
||||
if K < _W {
|
||||
K = _W
|
||||
}
|
||||
n = ((n / K) + 1) * K // round to a multiple of K
|
||||
return n / _W
|
||||
}
|
||||
|
||||
// poly represents an integer via a polynomial in Z[x]/(x^K+1)
|
||||
// where K is the FFT length and b^m is the computation basis 1<<(m*_W).
|
||||
// If P = a[0] + a[1] x + ... a[n] x^(K-1), the associated natural number
|
||||
// is P(b^m).
|
||||
type poly struct {
|
||||
k uint // k is such that K = 1<<k.
|
||||
m int // the m such that P(b^m) is the original number.
|
||||
a []nat // a slice of at most K m-word coefficients.
|
||||
}
|
||||
|
||||
// polyFromNat slices the number x into a polynomial
|
||||
// with 1<<k coefficients made of m words.
|
||||
func polyFromNat(x nat, k uint, m int) poly {
|
||||
p := poly{k: k, m: m}
|
||||
length := len(x)/m + 1
|
||||
p.a = make([]nat, length)
|
||||
for i := range p.a {
|
||||
if len(x) < m {
|
||||
p.a[i] = make(nat, m)
|
||||
copy(p.a[i], x)
|
||||
break
|
||||
}
|
||||
p.a[i] = x[:m]
|
||||
x = x[m:]
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Int evaluates back a poly to its integer value.
|
||||
func (p *poly) Int() nat {
|
||||
length := len(p.a)*p.m + 1
|
||||
if na := len(p.a); na > 0 {
|
||||
length += len(p.a[na-1])
|
||||
}
|
||||
n := make(nat, length)
|
||||
m := p.m
|
||||
np := n
|
||||
for i := range p.a {
|
||||
l := len(p.a[i])
|
||||
c := addVV(np[:l], np[:l], p.a[i])
|
||||
if np[l] < ^big.Word(0) {
|
||||
np[l] += c
|
||||
} else {
|
||||
addVW(np[l:], np[l:], c)
|
||||
}
|
||||
np = np[m:]
|
||||
}
|
||||
n = trim(n)
|
||||
return n
|
||||
}
|
||||
|
||||
func trim(n nat) nat {
|
||||
for i := range n {
|
||||
if n[len(n)-1-i] != 0 {
|
||||
return n[:len(n)-i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Mul multiplies p and q modulo X^K-1, where K = 1<<p.k.
|
||||
// The product is done via a Fourier transform.
|
||||
func (p *poly) Mul(q *poly) poly {
|
||||
// extra=2 because:
|
||||
// * some power of 2 is a K-th root of unity when n is a multiple of K/2.
|
||||
// * 2 itself is a square (see fermat.ShiftHalf)
|
||||
n := valueSize(p.k, p.m, 2)
|
||||
|
||||
pv, qv := p.Transform(n), q.Transform(n)
|
||||
rv := pv.Mul(&qv)
|
||||
r := rv.InvTransform()
|
||||
r.m = p.m
|
||||
return r
|
||||
}
|
||||
|
||||
// A polValues represents the value of a poly at the powers of a
|
||||
// K-th root of unity θ=2^(l/2) in Z/(b^n+1)Z, where b^n = 2^(K/4*l).
|
||||
type polValues struct {
|
||||
k uint // k is such that K = 1<<k.
|
||||
n int // the length of coefficients, n*_W a multiple of K/4.
|
||||
values []fermat // a slice of K (n+1)-word values
|
||||
}
|
||||
|
||||
// Transform evaluates p at θ^i for i = 0...K-1, where
|
||||
// θ is a K-th primitive root of unity in Z/(b^n+1)Z.
|
||||
func (p *poly) Transform(n int) polValues {
|
||||
k := p.k
|
||||
inputbits := make([]big.Word, (n+1)<<k)
|
||||
input := make([]fermat, 1<<k)
|
||||
// Now computed q(ω^i) for i = 0 ... K-1
|
||||
valbits := make([]big.Word, (n+1)<<k)
|
||||
values := make([]fermat, 1<<k)
|
||||
for i := range values {
|
||||
input[i] = inputbits[i*(n+1) : (i+1)*(n+1)]
|
||||
if i < len(p.a) {
|
||||
copy(input[i], p.a[i])
|
||||
}
|
||||
values[i] = fermat(valbits[i*(n+1) : (i+1)*(n+1)])
|
||||
}
|
||||
fourier(values, input, false, n, k)
|
||||
return polValues{k, n, values}
|
||||
}
|
||||
|
||||
// InvTransform reconstructs p (modulo X^K - 1) from its
|
||||
// values at θ^i for i = 0..K-1.
|
||||
func (v *polValues) InvTransform() poly {
|
||||
k, n := v.k, v.n
|
||||
|
||||
// Perform an inverse Fourier transform to recover p.
|
||||
pbits := make([]big.Word, (n+1)<<k)
|
||||
p := make([]fermat, 1<<k)
|
||||
for i := range p {
|
||||
p[i] = fermat(pbits[i*(n+1) : (i+1)*(n+1)])
|
||||
}
|
||||
fourier(p, v.values, true, n, k)
|
||||
// Divide by K, and untwist q to recover p.
|
||||
u := make(fermat, n+1)
|
||||
a := make([]nat, 1<<k)
|
||||
for i := range p {
|
||||
u.Shift(p[i], -int(k))
|
||||
copy(p[i], u)
|
||||
a[i] = nat(p[i])
|
||||
}
|
||||
return poly{k: k, m: 0, a: a}
|
||||
}
|
||||
|
||||
// NTransform evaluates p at θω^i for i = 0...K-1, where
|
||||
// θ is a (2K)-th primitive root of unity in Z/(b^n+1)Z
|
||||
// and ω = θ².
|
||||
func (p *poly) NTransform(n int) polValues {
|
||||
k := p.k
|
||||
if len(p.a) >= 1<<k {
|
||||
panic("Transform: len(p.a) >= 1<<k")
|
||||
}
|
||||
// θ is represented as a shift.
|
||||
θshift := (n * _W) >> k
|
||||
// p(x) = a_0 + a_1 x + ... + a_{K-1} x^(K-1)
|
||||
// p(θx) = q(x) where
|
||||
// q(x) = a_0 + θa_1 x + ... + θ^(K-1) a_{K-1} x^(K-1)
|
||||
//
|
||||
// Twist p by θ to obtain q.
|
||||
tbits := make([]big.Word, (n+1)<<k)
|
||||
twisted := make([]fermat, 1<<k)
|
||||
src := make(fermat, n+1)
|
||||
for i := range twisted {
|
||||
twisted[i] = fermat(tbits[i*(n+1) : (i+1)*(n+1)])
|
||||
if i < len(p.a) {
|
||||
for i := range src {
|
||||
src[i] = 0
|
||||
}
|
||||
copy(src, p.a[i])
|
||||
twisted[i].Shift(src, θshift*i)
|
||||
}
|
||||
}
|
||||
|
||||
// Now computed q(ω^i) for i = 0 ... K-1
|
||||
valbits := make([]big.Word, (n+1)<<k)
|
||||
values := make([]fermat, 1<<k)
|
||||
for i := range values {
|
||||
values[i] = fermat(valbits[i*(n+1) : (i+1)*(n+1)])
|
||||
}
|
||||
fourier(values, twisted, false, n, k)
|
||||
return polValues{k, n, values}
|
||||
}
|
||||
|
||||
// InvTransform reconstructs a polynomial from its values at
|
||||
// roots of x^K+1. The m field of the returned polynomial
|
||||
// is unspecified.
|
||||
func (v *polValues) InvNTransform() poly {
|
||||
k := v.k
|
||||
n := v.n
|
||||
θshift := (n * _W) >> k
|
||||
|
||||
// Perform an inverse Fourier transform to recover q.
|
||||
qbits := make([]big.Word, (n+1)<<k)
|
||||
q := make([]fermat, 1<<k)
|
||||
for i := range q {
|
||||
q[i] = fermat(qbits[i*(n+1) : (i+1)*(n+1)])
|
||||
}
|
||||
fourier(q, v.values, true, n, k)
|
||||
|
||||
// Divide by K, and untwist q to recover p.
|
||||
u := make(fermat, n+1)
|
||||
a := make([]nat, 1<<k)
|
||||
for i := range q {
|
||||
u.Shift(q[i], -int(k)-i*θshift)
|
||||
copy(q[i], u)
|
||||
a[i] = nat(q[i])
|
||||
}
|
||||
return poly{k: k, m: 0, a: a}
|
||||
}
|
||||
|
||||
// fourier performs an unnormalized Fourier transform
|
||||
// of src, a length 1<<k vector of numbers modulo b^n+1
|
||||
// where b = 1<<_W.
|
||||
func fourier(dst []fermat, src []fermat, backward bool, n int, k uint) {
|
||||
var rec func(dst, src []fermat, size uint)
|
||||
tmp := make(fermat, n+1) // pre-allocate temporary variables.
|
||||
tmp2 := make(fermat, n+1) // pre-allocate temporary variables.
|
||||
|
||||
// The recursion function of the FFT.
|
||||
// The root of unity used in the transform is ω=1<<(ω2shift/2).
|
||||
// The source array may use shifted indices (i.e. the i-th
|
||||
// element is src[i << idxShift]).
|
||||
rec = func(dst, src []fermat, size uint) {
|
||||
idxShift := k - size
|
||||
ω2shift := (4 * n * _W) >> size
|
||||
if backward {
|
||||
ω2shift = -ω2shift
|
||||
}
|
||||
|
||||
// Easy cases.
|
||||
if len(src[0]) != n+1 || len(dst[0]) != n+1 {
|
||||
panic("len(src[0]) != n+1 || len(dst[0]) != n+1")
|
||||
}
|
||||
switch size {
|
||||
case 0:
|
||||
copy(dst[0], src[0])
|
||||
return
|
||||
case 1:
|
||||
dst[0].Add(src[0], src[1<<idxShift]) // dst[0] = src[0] + src[1]
|
||||
dst[1].Sub(src[0], src[1<<idxShift]) // dst[1] = src[0] - src[1]
|
||||
return
|
||||
}
|
||||
|
||||
// Let P(x) = src[0] + src[1<<idxShift] * x + ... + src[K-1 << idxShift] * x^(K-1)
|
||||
// The P(x) = Q1(x²) + x*Q2(x²)
|
||||
// where Q1's coefficients are src with indices shifted by 1
|
||||
// where Q2's coefficients are src[1<<idxShift:] with indices shifted by 1
|
||||
|
||||
// Split destination vectors in halves.
|
||||
dst1 := dst[:1<<(size-1)]
|
||||
dst2 := dst[1<<(size-1):]
|
||||
// Transform Q1 and Q2 in the halves.
|
||||
rec(dst1, src, size-1)
|
||||
rec(dst2, src[1<<idxShift:], size-1)
|
||||
|
||||
// Reconstruct P's transform from transforms of Q1 and Q2.
|
||||
// dst[i] is dst1[i] + ω^i * dst2[i]
|
||||
// dst[i + 1<<(k-1)] is dst1[i] + ω^(i+K/2) * dst2[i]
|
||||
//
|
||||
for i := range dst1 {
|
||||
tmp.ShiftHalf(dst2[i], i*ω2shift, tmp2) // ω^i * dst2[i]
|
||||
dst2[i].Sub(dst1[i], tmp)
|
||||
dst1[i].Add(dst1[i], tmp)
|
||||
}
|
||||
}
|
||||
rec(dst, src, k)
|
||||
}
|
||||
|
||||
// Mul returns the pointwise product of p and q.
|
||||
func (p *polValues) Mul(q *polValues) (r polValues) {
|
||||
n := p.n
|
||||
r.k, r.n = p.k, p.n
|
||||
r.values = make([]fermat, len(p.values))
|
||||
bits := make([]big.Word, len(p.values)*(n+1))
|
||||
buf := make(fermat, 8*n)
|
||||
for i := range r.values {
|
||||
r.values[i] = bits[i*(n+1) : (i+1)*(n+1)]
|
||||
z := buf.Mul(p.values[i], q.values[i])
|
||||
copy(r.values[i], z)
|
||||
}
|
||||
return
|
||||
}
|
||||
70
vendor/github.com/remyoudompheng/bigfft/scan.go
generated
vendored
Normal file
70
vendor/github.com/remyoudompheng/bigfft/scan.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
package bigfft
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// FromDecimalString converts the base 10 string
|
||||
// representation of a natural (non-negative) number
|
||||
// into a *big.Int.
|
||||
// Its asymptotic complexity is less than quadratic.
|
||||
func FromDecimalString(s string) *big.Int {
|
||||
var sc scanner
|
||||
z := new(big.Int)
|
||||
sc.scan(z, s)
|
||||
return z
|
||||
}
|
||||
|
||||
type scanner struct {
|
||||
// powers[i] is 10^(2^i * quadraticScanThreshold).
|
||||
powers []*big.Int
|
||||
}
|
||||
|
||||
func (s *scanner) chunkSize(size int) (int, *big.Int) {
|
||||
if size <= quadraticScanThreshold {
|
||||
panic("size < quadraticScanThreshold")
|
||||
}
|
||||
pow := uint(0)
|
||||
for n := size; n > quadraticScanThreshold; n /= 2 {
|
||||
pow++
|
||||
}
|
||||
// threshold * 2^(pow-1) <= size < threshold * 2^pow
|
||||
return quadraticScanThreshold << (pow - 1), s.power(pow - 1)
|
||||
}
|
||||
|
||||
func (s *scanner) power(k uint) *big.Int {
|
||||
for i := len(s.powers); i <= int(k); i++ {
|
||||
z := new(big.Int)
|
||||
if i == 0 {
|
||||
if quadraticScanThreshold%14 != 0 {
|
||||
panic("quadraticScanThreshold % 14 != 0")
|
||||
}
|
||||
z.Exp(big.NewInt(1e14), big.NewInt(quadraticScanThreshold/14), nil)
|
||||
} else {
|
||||
z.Mul(s.powers[i-1], s.powers[i-1])
|
||||
}
|
||||
s.powers = append(s.powers, z)
|
||||
}
|
||||
return s.powers[k]
|
||||
}
|
||||
|
||||
func (s *scanner) scan(z *big.Int, str string) {
|
||||
if len(str) <= quadraticScanThreshold {
|
||||
z.SetString(str, 10)
|
||||
return
|
||||
}
|
||||
sz, pow := s.chunkSize(len(str))
|
||||
// Scan the left half.
|
||||
s.scan(z, str[:len(str)-sz])
|
||||
// FIXME: reuse temporaries.
|
||||
left := Mul(z, pow)
|
||||
// Scan the right half
|
||||
s.scan(z, str[len(str)-sz:])
|
||||
z.Add(z, left)
|
||||
}
|
||||
|
||||
// quadraticScanThreshold is the number of digits
|
||||
// below which big.Int.SetString is more efficient
|
||||
// than subquadratic algorithms.
|
||||
// 1232 digits fit in 4096 bits.
|
||||
const quadraticScanThreshold = 1232
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user