Linter updates
This commit is contained in:
parent
a1969b681a
commit
048e062102
@ -1,43 +1,105 @@
|
||||
---
|
||||
linters:
|
||||
enable:
|
||||
- deadcode
|
||||
- errcheck
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- typecheck
|
||||
- unused
|
||||
- varcheck
|
||||
|
||||
- asciicheck
|
||||
- bidichk
|
||||
- bodyclose
|
||||
- dogsled
|
||||
- containedctx
|
||||
- contextcheck
|
||||
- cyclop
|
||||
- decorder
|
||||
- depguard
|
||||
- dupl
|
||||
- durationcheck
|
||||
- errchkjson
|
||||
- errname
|
||||
- errorlint
|
||||
- exhaustive
|
||||
- exhaustivestruct
|
||||
- exportloopref
|
||||
- forcetypeassert
|
||||
- funlen
|
||||
- gci
|
||||
- gochecknoinits
|
||||
- gocognit
|
||||
- goconst
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- goerr113
|
||||
- gofumpt
|
||||
# - gocyclo # Using cyclop
|
||||
- godot
|
||||
# - goerr113 # Using errorlint
|
||||
- gofmt
|
||||
- goheader
|
||||
- goimports
|
||||
- gomnd
|
||||
- gomoddirectives
|
||||
- gomodguard
|
||||
- goprintffuncname
|
||||
# - gosec
|
||||
# - ifshort
|
||||
- interfacer
|
||||
- maligned
|
||||
- gosec
|
||||
- grouper
|
||||
- ifshort
|
||||
- importas
|
||||
# - ireturn
|
||||
- lll
|
||||
- maintidx
|
||||
- makezero
|
||||
- misspell
|
||||
- nakedret
|
||||
- nestif
|
||||
- nilerr
|
||||
- nilnil
|
||||
- nlreturn
|
||||
- noctx
|
||||
- nolintlint
|
||||
- paralleltest
|
||||
- prealloc
|
||||
- predeclared
|
||||
# - promlinter # Not common enough
|
||||
- revive
|
||||
- rowserrcheck
|
||||
- sqlclosecheck
|
||||
# - stylecheck # Using revive
|
||||
- tagliatelle
|
||||
- tenv
|
||||
- testpackage
|
||||
- thelper
|
||||
- tparallel
|
||||
- unconvert
|
||||
- unparam
|
||||
- varnamelen
|
||||
- wastedassign
|
||||
- whitespace
|
||||
- wrapcheck
|
||||
- wsl
|
||||
# - errorlint
|
||||
|
||||
disable:
|
||||
- gochecknoglobals
|
||||
- godox
|
||||
- forbidigo
|
||||
# Deprecated
|
||||
- golint
|
||||
- interfacer
|
||||
- maligned
|
||||
- scopelint
|
||||
|
||||
linters-settings:
|
||||
gosec:
|
||||
excludes:
|
||||
- G204
|
||||
# gomnd:
|
||||
# settings:
|
||||
# mnd:
|
||||
# ignored-functions: math.*
|
||||
# gosec:
|
||||
# excludes:
|
||||
# - G204
|
||||
gomnd:
|
||||
settings:
|
||||
mnd:
|
||||
ignored-functions: math.*
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
@ -45,4 +107,5 @@ issues:
|
||||
linters:
|
||||
- errcheck
|
||||
- gosec
|
||||
- maligned
|
||||
# Enable autofix
|
||||
fix: true
|
||||
|
3
go.mod
3
go.mod
@ -2,11 +2,12 @@ module git.iamthefij.com/iamthefij/restic-scheduler
|
||||
|
||||
go 1.17
|
||||
|
||||
require github.com/hashicorp/hcl/v2 v2.11.1
|
||||
|
||||
require (
|
||||
github.com/agext/levenshtein v1.2.1 // indirect
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
|
||||
github.com/google/go-cmp v0.3.1 // indirect
|
||||
github.com/hashicorp/hcl/v2 v2.11.1 // indirect
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
|
||||
github.com/zclconf/go-cty v1.8.0 // indirect
|
||||
golang.org/x/text v0.3.5 // indirect
|
||||
|
6
go.sum
6
go.sum
@ -5,7 +5,9 @@ github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2
|
||||
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
|
||||
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/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
||||
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@ -14,13 +16,17 @@ github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/hashicorp/hcl/v2 v2.11.1 h1:yTyWcXcm9XB0TEkyU/JCRU6rYy4K+mgLtzn2wlrJbcc=
|
||||
github.com/hashicorp/hcl/v2 v2.11.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
|
40
job.go
40
job.go
@ -16,7 +16,7 @@ type TaskConfig struct {
|
||||
Restic *ResticCmd
|
||||
}
|
||||
|
||||
// ResticConfig is all configuration to be sent to Restic
|
||||
// ResticConfig is all configuration to be sent to Restic.
|
||||
type ResticConfig struct {
|
||||
Repo string `hcl:"repo"`
|
||||
Passphrase string `hcl:"passphrase,optional"`
|
||||
@ -24,14 +24,14 @@ type ResticConfig struct {
|
||||
GlobalOpts *ResticGlobalOpts `hcl:"options,block"`
|
||||
}
|
||||
|
||||
// ExecutableTask is a task to be run before or after backup/retore
|
||||
// ExecutableTask is a task to be run before or after backup/retore.
|
||||
type ExecutableTask interface {
|
||||
RunBackup(cfg TaskConfig) error
|
||||
RunRestore(cfg TaskConfig) error
|
||||
Name() string
|
||||
}
|
||||
|
||||
// JobTaskScript is a sript to be executed as part of a job task
|
||||
// JobTaskScript is a sript to be executed as part of a job task.
|
||||
type JobTaskScript struct {
|
||||
OnBackup string `hcl:"on_backup,optional"`
|
||||
OnRestore string `hcl:"on_restore,optional"`
|
||||
@ -40,7 +40,7 @@ type JobTaskScript struct {
|
||||
name string
|
||||
}
|
||||
|
||||
// RunBackup runs script on backup
|
||||
// RunBackup runs script on backup.
|
||||
func (t JobTaskScript) RunBackup(cfg TaskConfig) error {
|
||||
env := MergeEnv(cfg.Env, t.env)
|
||||
if env == nil {
|
||||
@ -61,7 +61,7 @@ func (t JobTaskScript) RunBackup(cfg TaskConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunRestore script on restore
|
||||
// RunRestore script on restore.
|
||||
func (t JobTaskScript) RunRestore(cfg TaskConfig) error {
|
||||
env := MergeEnv(cfg.Env, t.env)
|
||||
if env == nil {
|
||||
@ -90,7 +90,7 @@ func (t *JobTaskScript) SetName(name string) {
|
||||
t.name = name
|
||||
}
|
||||
|
||||
// JobTaskMySQL is a sqlite backup task that performs required pre and post tasks
|
||||
// JobTaskMySQL is a sqlite backup task that performs required pre and post tasks.
|
||||
type JobTaskMySQL struct {
|
||||
Name string `hcl:"name,label"`
|
||||
Hostname string `hcl:"hostname,optional"`
|
||||
@ -102,6 +102,7 @@ type JobTaskMySQL struct {
|
||||
func (t JobTaskMySQL) GetPreTask() ExecutableTask {
|
||||
return JobTaskScript{
|
||||
name: t.Name,
|
||||
env: nil,
|
||||
OnBackup: fmt.Sprintf(
|
||||
"mysqldump -h '%s' -u '%s' -p '%s' '%s' > './%s.sql'",
|
||||
t.Hostname,
|
||||
@ -110,6 +111,7 @@ func (t JobTaskMySQL) GetPreTask() ExecutableTask {
|
||||
t.Database,
|
||||
t.Name,
|
||||
),
|
||||
OnRestore: "",
|
||||
FromJobDir: true,
|
||||
}
|
||||
}
|
||||
@ -117,6 +119,8 @@ func (t JobTaskMySQL) GetPreTask() ExecutableTask {
|
||||
func (t JobTaskMySQL) GetPostTask() ExecutableTask {
|
||||
return JobTaskScript{
|
||||
name: t.Name,
|
||||
env: nil,
|
||||
OnBackup: "",
|
||||
OnRestore: fmt.Sprintf(
|
||||
"mysql -h '%s' -u '%s' -p '%s' '%s' << './%s.sql'",
|
||||
t.Hostname,
|
||||
@ -129,7 +133,7 @@ func (t JobTaskMySQL) GetPostTask() ExecutableTask {
|
||||
}
|
||||
}
|
||||
|
||||
// JobTaskSqlite is a sqlite backup task that performs required pre and post tasks
|
||||
// JobTaskSqlite is a sqlite backup task that performs required pre and post tasks.
|
||||
type JobTaskSqlite struct {
|
||||
Name string `hcl:"name,label"`
|
||||
Path string `hcl:"path"`
|
||||
@ -138,17 +142,23 @@ type JobTaskSqlite struct {
|
||||
func (t JobTaskSqlite) GetPreTask() ExecutableTask {
|
||||
return JobTaskScript{
|
||||
name: t.Name,
|
||||
env: nil,
|
||||
OnBackup: fmt.Sprintf(
|
||||
"sqlite3 %s '.backup $RESTIC_JOB_DIR/%s.bak'",
|
||||
t.Path, t.Name,
|
||||
),
|
||||
OnRestore: "",
|
||||
FromJobDir: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (t JobTaskSqlite) GetPostTask() ExecutableTask {
|
||||
return JobTaskScript{
|
||||
name: t.Name,
|
||||
env: nil,
|
||||
OnBackup: "",
|
||||
OnRestore: fmt.Sprintf("cp '$RESTIC_JOB_DIR/%s.bak' '%s'", t.Name, t.Path),
|
||||
FromJobDir: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,7 +199,7 @@ func (t *BackupFilesTask) SetName(name string) {
|
||||
t.name = name
|
||||
}
|
||||
|
||||
// JobTask represents a single task within a backup job
|
||||
// JobTask represents a single task within a backup job.
|
||||
type JobTask struct {
|
||||
Name string `hcl:"name,label"`
|
||||
Scripts []JobTaskScript `hcl:"script,block"`
|
||||
@ -213,7 +223,7 @@ func (t JobTask) GetTasks() []ExecutableTask {
|
||||
}
|
||||
|
||||
// Job contains all configuration required to construct and run a backup
|
||||
// and restore job
|
||||
// and restore job.
|
||||
type Job struct {
|
||||
Name string `hcl:"name,label"`
|
||||
Schedule string `hcl:"schedule"`
|
||||
@ -223,7 +233,7 @@ type Job struct {
|
||||
Forget *ForgetOpts `hcl:"forget,block"`
|
||||
|
||||
// Meta Tasks
|
||||
MySql []JobTaskMySQL `hcl:"mysql,block"`
|
||||
MySQL []JobTaskMySQL `hcl:"mysql,block"`
|
||||
Sqlite []JobTaskSqlite `hcl:"sqlite,block"`
|
||||
}
|
||||
|
||||
@ -231,7 +241,7 @@ func (j Job) AllTasks() []ExecutableTask {
|
||||
allTasks := []ExecutableTask{}
|
||||
|
||||
// Pre tasks
|
||||
for _, mysql := range j.MySql {
|
||||
for _, mysql := range j.MySQL {
|
||||
allTasks = append(allTasks, mysql.GetPreTask())
|
||||
}
|
||||
|
||||
@ -245,7 +255,7 @@ func (j Job) AllTasks() []ExecutableTask {
|
||||
}
|
||||
|
||||
// Post tasks
|
||||
for _, mysql := range j.MySql {
|
||||
for _, mysql := range j.MySQL {
|
||||
allTasks = append(allTasks, mysql.GetPreTask())
|
||||
}
|
||||
|
||||
@ -277,6 +287,7 @@ func (j Job) RunTasks() error {
|
||||
JobDir: jobDir,
|
||||
Logger: GetChildLogger(logger, exTask.Name()),
|
||||
Restic: restic,
|
||||
Env: nil,
|
||||
}
|
||||
|
||||
if err := exTask.RunBackup(taskCfg); err != nil {
|
||||
@ -285,7 +296,9 @@ func (j Job) RunTasks() error {
|
||||
}
|
||||
|
||||
if j.Forget != nil {
|
||||
restic.Forget(j.Forget)
|
||||
if err := restic.Forget(j.Forget); err != nil {
|
||||
return fmt.Errorf("failed forgetting and pruning job %s: %w", j.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -298,6 +311,7 @@ func (j Job) NewRestic() *ResticCmd {
|
||||
Env: j.Config.Env,
|
||||
Passphrase: j.Config.Passphrase,
|
||||
GlobalOpts: j.Config.GlobalOpts,
|
||||
Cwd: "",
|
||||
}
|
||||
}
|
||||
|
||||
|
2
main.go
2
main.go
@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// version of restic-scheduler being run
|
||||
// version of restic-scheduler being run.
|
||||
version = "dev"
|
||||
)
|
||||
|
||||
|
22
restic.go
22
restic.go
@ -15,7 +15,7 @@ type CommandOptions interface {
|
||||
|
||||
type NoOpts struct{}
|
||||
|
||||
func (_ NoOpts) ToArgs() []string {
|
||||
func (NoOpts) ToArgs() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ type ResticGlobalOpts struct {
|
||||
CaCertFile string `hcl:"CaCertFile,optional"`
|
||||
CacheDir string `hcl:"CacheDir,optional"`
|
||||
PasswordFile string `hcl:"PasswordFile,optional"`
|
||||
TlsClientCertFile string `hcl:"TlsClientCertFile,optional"`
|
||||
TLSClientCertFile string `hcl:"TlsClientCertFile,optional"`
|
||||
LimitDownload int `hcl:"LimitDownload,optional"`
|
||||
LimitUpload int `hcl:"LimitUpload,optional"`
|
||||
VerboseLevel int `hcl:"VerboseLevel,optional"`
|
||||
@ -32,6 +32,7 @@ type ResticGlobalOpts struct {
|
||||
NoLock bool `hcl:"NoLock,optional"`
|
||||
}
|
||||
|
||||
// nolint:cyclop
|
||||
func (glo ResticGlobalOpts) ToArgs() (args []string) {
|
||||
if glo.CaCertFile != "" {
|
||||
args = append(args, "--cacert", glo.CaCertFile)
|
||||
@ -65,8 +66,8 @@ func (glo ResticGlobalOpts) ToArgs() (args []string) {
|
||||
args = append(args, "--password-file", glo.PasswordFile)
|
||||
}
|
||||
|
||||
if glo.TlsClientCertFile != "" {
|
||||
args = append(args, "--tls-client-cert", glo.TlsClientCertFile)
|
||||
if glo.TLSClientCertFile != "" {
|
||||
args = append(args, "--tls-client-cert", glo.TLSClientCertFile)
|
||||
}
|
||||
|
||||
if glo.VerboseLevel > 0 {
|
||||
@ -120,9 +121,11 @@ func (rcmd ResticCmd) RunRestic(command string, options CommandOptions, commandA
|
||||
cmd.Env = rcmd.BuildEnv()
|
||||
cmd.Dir = rcmd.Cwd
|
||||
|
||||
err := cmd.Run()
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("error running restic: %w", err)
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
type BackupOpts struct {
|
||||
@ -146,7 +149,7 @@ func (bo BackupOpts) ToArgs() (args []string) {
|
||||
|
||||
func (rcmd ResticCmd) Backup(files []string, options *BackupOpts) error {
|
||||
if options == nil {
|
||||
options = &BackupOpts{}
|
||||
options = &BackupOpts{} // nolint:exhaustivestruct
|
||||
}
|
||||
|
||||
err := rcmd.RunRestic("backup", options, files...)
|
||||
@ -198,7 +201,7 @@ func (ro RestoreOpts) ToArgs() (args []string) {
|
||||
|
||||
func (rcmd ResticCmd) Restore(snapshot string, opts *RestoreOpts) error {
|
||||
if opts == nil {
|
||||
opts = &RestoreOpts{}
|
||||
opts = &RestoreOpts{} // nolint:exhaustivestruct
|
||||
}
|
||||
|
||||
err := rcmd.RunRestic("restore", opts, snapshot)
|
||||
@ -227,6 +230,7 @@ type ForgetOpts struct {
|
||||
Prune bool `hcl:"Prune,optional"`
|
||||
}
|
||||
|
||||
// nolint:funlen,cyclop
|
||||
func (fo ForgetOpts) ToArgs() (args []string) {
|
||||
// Add keep-*
|
||||
if fo.KeepLast > 0 {
|
||||
@ -298,7 +302,7 @@ func (fo ForgetOpts) ToArgs() (args []string) {
|
||||
|
||||
func (rcmd ResticCmd) Forget(forgetOpts *ForgetOpts) error {
|
||||
if forgetOpts == nil {
|
||||
forgetOpts = &ForgetOpts{}
|
||||
forgetOpts = &ForgetOpts{} // nolint:exhaustivestruct
|
||||
}
|
||||
|
||||
err := rcmd.RunRestic("forget", forgetOpts)
|
||||
|
14
shell.go
14
shell.go
@ -30,25 +30,25 @@ func GetChildLogger(parent *log.Logger, name string) *log.Logger {
|
||||
return GetLogger(childName)
|
||||
}
|
||||
|
||||
type logWriter struct {
|
||||
type LogWriter struct {
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func NewLogWriter(logger *log.Logger) *logWriter {
|
||||
return &logWriter{logger}
|
||||
func NewLogWriter(logger *log.Logger) *LogWriter {
|
||||
return &LogWriter{logger}
|
||||
}
|
||||
|
||||
func (w logWriter) Write(p []byte) (n int, err error) {
|
||||
message := fmt.Sprintf("%s", p)
|
||||
func (w LogWriter) Write(content []byte) (n int, err error) {
|
||||
message := string(content)
|
||||
for _, line := range strings.Split(message, "\n") {
|
||||
w.logger.Printf(" %s", line)
|
||||
}
|
||||
|
||||
return len(p), nil
|
||||
return len(content), nil
|
||||
}
|
||||
|
||||
func RunShell(script string, cwd string, env map[string]string, logger *log.Logger) error {
|
||||
cmd := exec.Command("sh", "-c", strings.TrimSpace(script))
|
||||
cmd := exec.Command("sh", "-c", strings.TrimSpace(script)) // nolint:gosec
|
||||
|
||||
// Make both stderr and stdout go to logger
|
||||
// fmt.Println("LOGGER PREFIX", logger.Prefix())
|
||||
|
6
utils.go
6
utils.go
@ -1,6 +1,8 @@
|
||||
package main
|
||||
|
||||
func MergeEnv(parent, child map[string]string) (result map[string]string) {
|
||||
func MergeEnv(parent, child map[string]string) map[string]string {
|
||||
result := map[string]string{}
|
||||
|
||||
for key, value := range parent {
|
||||
result[key] = value
|
||||
}
|
||||
@ -9,5 +11,5 @@ func MergeEnv(parent, child map[string]string) (result map[string]string) {
|
||||
result[key] = value
|
||||
}
|
||||
|
||||
return
|
||||
return result
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user