WIP update deps, sql builder instead of jet

This commit is contained in:
2025-03-10 10:29:18 -04:00
parent cb3b1a429c
commit 13747c2118
87 changed files with 5208 additions and 2523 deletions

View File

@@ -1,52 +0,0 @@
package main
import (
"fmt"
"os"
"os/exec"
"runtime"
)
func compileJet() {
fmt.Printf("Compiling Jet generator")
os.Setenv("CGO_ENABLED", "1")
cmd := exec.Command("go", "build", "./cmd/jet")
cmd.Dir = "./tools/jet-2.12.0"
handleCmdOutput(cmd.CombinedOutput())
}
func generateJetModels() {
bin := ""
jetdir := ".jet"
if runtime.GOOS == "windows" {
bin = "./tools/jet-2.12.0/jet.exe"
} else {
bin = "./tools/jet-2.12.0/jet"
}
os.RemoveAll(jetdir)
// compile bin if not exists
if _, err := os.Stat(bin); err != nil {
compileJet()
}
fmt.Printf("Generating SQL models (jet)")
if _, err := os.Stat("passwords.db"); err != nil {
printStatus(false)
fmt.Println("\n" + err.Error())
os.Exit(1)
}
databaseType := "sqlite"
cmd := exec.Command(bin, "-source="+databaseType, "-dsn=file:passwords.db", "-schema=maxwarden", "-path="+jetdir)
handleCmdOutput(cmd.CombinedOutput())
printStatus(true)
}

View File

@@ -4,7 +4,6 @@ import (
"flag" "flag"
"fmt" "fmt"
"os" "os"
"os/exec"
) )
var envtype int // set by user with cli flag var envtype int // set by user with cli flag
@@ -43,10 +42,6 @@ func main() {
for _, arg := range args { for _, arg := range args {
switch arg { switch arg {
case "build-all":
preBuild()
build()
goto End
case "build": case "build":
preBuild() preBuild()
goto End goto End
@@ -86,26 +81,4 @@ func preBuild() {
// code generation // code generation
generateInlineStyles() generateInlineStyles()
generateDebugConfig() generateDebugConfig()
generateJetModels()
}
func build() {
compileServer()
}
func compileServer() {
var out []byte
var err error
fmt.Printf("Compiling Server Binary")
if envtype == ENVIRONMENT_DEV {
// include extra flags for the GC
out, err = exec.Command("go", "build", "-gcflags=all=-N -l", "./cmd/server").CombinedOutput()
} else {
out, err = exec.Command("go", "build", "./cmd/server").CombinedOutput()
}
handleCmdOutput(out, err)
printStatus(true)
} }

459
database/builder.go Normal file
View File

@@ -0,0 +1,459 @@
package database
import (
"database/sql"
"errors"
"fmt"
"reflect"
"regexp"
"github.com/jmoiron/sqlx"
)
type QueryBuilder struct {
BaseSQL string // initial sql string to build query from
Subquery bool // wraps query in parenthesis
Single bool // returns single entity
Paginate bool
ItemsPerPage int
PageNum int // index from 1
OrderBy []string
OrderDescending bool
GroupBy []string
Where []QueryFilter
Setters []QuerySetter // used for insert and update compilation
}
type QueryFilter struct {
Unsafe bool // disables validation if true
Column string
Operator int // const enum
Parameter interface{} // not used if subquery not nil
SubqueryBuilder *QueryBuilder // builds a SELECT subquery
}
type QueryBetween struct {
First interface{}
Second interface{}
}
type QuerySetter struct {
Column string // column to set
Parameter interface{} // parameter to set IF THERE IS NO SUBQUERY
SubqueryBuilder *QueryBuilder // builds a SELECT subquery - NOTE - if the subquery contains filters, those filters will automatically append to the parameter list for the generated query
}
// filter operators
const (
EQ = iota // equal
NE = iota // not equal
GT = iota // greater than
LT = iota // less than
GE = iota // greater than or equal to
LE = iota // less than or equal to
LIKE = iota // like
BETWEEN = iota // between
)
func Update[T any](qb *QueryBuilder, db *sqlx.DB, manualParams ...interface{}) (sql.Result, error) {
sql, params, buildErr := buildUpdate[T](qb)
if buildErr != nil {
return nil, buildErr
}
params = append(params, manualParams)
return db.Exec(sql, params...)
}
func Insert[T any](qb *QueryBuilder, db *sqlx.DB, manualParams ...interface{}) (sql.Result, error) {
sql, params, buildErr := buildInsert[T](qb)
if buildErr != nil {
return nil, buildErr
}
params = append(params, manualParams)
return db.Exec(sql, params...)
}
func Delete[T any](qb *QueryBuilder, db *sqlx.DB, manualParams ...interface{}) (sql.Result, error) {
sql, params, buildErr := buildDelete[T](qb)
if buildErr != nil {
return nil, buildErr
}
params = append(params, manualParams)
return db.Exec(sql, params...)
}
func Select[T any](qb *QueryBuilder, db *sqlx.DB, manualParams ...interface{}) ([]T, error) {
var entities []T
sql, params, buildErr := buildSelect[T](qb)
if buildErr != nil {
return nil, buildErr
}
params = append(params, manualParams)
err := db.Select(&entities, sql, params...)
return entities, err
}
func Get[T any](qb *QueryBuilder, db *sqlx.DB, manualParams ...interface{}) (T, error) {
var entity T
qb.Single = true
sql, params, buildErr := buildSelect[T](qb)
if buildErr != nil {
return entity, buildErr
}
params = append(params, manualParams)
err := db.Get(&entity, sql, params...)
return entity, err
}
// internal functions
func buildWhere[T any](qb *QueryBuilder) (string, []interface{}, error) {
var sql string
sql += " "
if qb == nil {
return "", nil, errors.New("no query filter provided")
}
if len(qb.Where) == 0 {
return "", nil, nil
}
var params []interface{}
for i, v := range qb.Where {
if !v.Unsafe && !validateSQLColName[T](v.Column) {
return "", nil, errors.New("invalid column name provided in WHERE clause. column: " + v.Column)
}
if i == 0 {
sql += "WHERE "
} else {
sql += "AND "
}
sql += v.Column + " "
// single param operators
switch v.Operator {
case EQ:
sql += "= "
case NE:
sql += "<> "
case GT:
sql += "> "
case LT:
sql += "< "
case GE:
sql += ">= "
case LE:
sql += "<= "
case LIKE:
sql += "LIKE "
case BETWEEN:
sql += "BETWEEN ? AND ? "
}
if v.SubqueryBuilder == nil && v.Operator != BETWEEN {
sql += "? "
}
if v.Parameter == nil {
return "", nil, errors.New("WHERE clause not nil, but parameters nil for column: " + v.Column)
}
between, isBetween := v.Parameter.(QueryBetween)
if v.Operator == BETWEEN {
// between does not work with subqueries atm
if !isBetween {
return "", nil, errors.New("Filter parameter must be of type `QueryBetween` when using the BETWEEN operator. Column: " + v.Column)
}
params = append(params, between.First)
params = append(params, between.Second)
} else {
if isBetween {
return "", nil, errors.New("Attempt to use a between filter for a non between query. Column: " + v.Column)
}
if v.SubqueryBuilder != nil {
// recurse!
rSql, rParams, subqueryErr := buildSelect[T](v.SubqueryBuilder)
if subqueryErr != nil {
return "", nil, subqueryErr
}
sql += rSql + " "
params = append(params, rParams...)
} else {
params = append(params, v.Parameter)
}
}
}
return sql, params, nil
}
func buildSelect[T any](qb *QueryBuilder) (string, []interface{}, error) {
if qb == nil {
return "", nil, errors.New("no query filter provided")
}
sql := qb.BaseSQL
var params []interface{}
sql += " "
// where filters
wSql, wParams, wErr := buildWhere[T](qb)
if wErr != nil {
return "", nil, wErr
}
params = append(params, wParams...)
sql += wSql
// grouping
if len(qb.GroupBy) > 0 {
sql += "GROUP BY "
for i, v := range qb.GroupBy {
if !validateSQLColName[T](v) {
return "", nil, errors.New("invalid name for groupby clause")
}
if i == (len(qb.GroupBy) - 1) {
sql += v + " "
} else {
sql += v + ", "
}
}
}
// ordering
if len(qb.OrderBy) > 0 {
sql += "ORDER BY "
for i, v := range qb.OrderBy {
if !validateSQLColName[T](v) {
return "", nil, errors.New("invalid name for orderby clause")
}
if i == (len(qb.OrderBy) - 1) {
sql += v + " "
} else {
sql += v + ", "
}
}
if qb.OrderDescending {
sql += "DESC "
} else {
sql += "ASC "
}
}
// return first result
if qb.Single {
sql += "LIMIT 1"
}
// pagination
if !qb.Single && qb.Paginate {
if qb.PageNum <= 0 {
qb.PageNum = 1
}
if qb.ItemsPerPage <= 0 {
qb.ItemsPerPage = 10
}
sql += fmt.Sprintf("LIMIT %d ", qb.ItemsPerPage)
offset := (qb.PageNum - 1) * qb.ItemsPerPage
sql += fmt.Sprintf("OFFSET %d", offset)
}
if qb.Subquery {
sql = "(" + sql + ")"
}
return sql, params, nil
}
func buildInsert[T any](qb *QueryBuilder) (string, []interface{}, error) {
sql := qb.BaseSQL
sql += " ("
var columns []string
var parameters []interface{}
for _, k := range qb.Setters {
columns = append(columns, k.Column)
}
if len(columns) == 0 {
return "", nil, errors.New("one or more setters contains invalid column name")
}
for i, v := range columns {
if i == len(columns)-1 {
sql += v
} else {
sql += v + ","
}
}
sql += ") VALUES ("
for i, v := range qb.Setters {
var subSql string
var subParams []interface{}
var buildSubErr error
if v.SubqueryBuilder != nil {
subSql, subParams, buildSubErr = buildSelect[T](v.SubqueryBuilder)
if buildSubErr != nil {
return "", nil, buildSubErr
}
parameters = append(parameters, subParams...)
} else {
parameters = append(parameters, v.Parameter)
}
if i == len(columns)-1 {
if v.SubqueryBuilder != nil {
sql += subSql
} else {
sql += "?"
}
} else {
if v.SubqueryBuilder != nil {
sql += subSql + ","
} else {
sql += "?,"
}
}
}
sql += ") "
return sql, parameters, nil
}
func buildUpdate[T any](qb *QueryBuilder) (string, []interface{}, error) {
sql := qb.BaseSQL
sql += " "
var params []interface{}
for i, v := range qb.Setters {
var subSql string
var subParams []interface{}
var buildSubErr error
if v.SubqueryBuilder != nil {
subSql, subParams, buildSubErr = buildSelect[T](v.SubqueryBuilder)
if buildSubErr != nil {
return "", nil, buildSubErr
}
params = append(params, subParams...)
} else {
params = append(params, v.Parameter)
}
if i == len(qb.Setters)-1 && i == 0 {
if v.SubqueryBuilder != nil {
sql += "SET " + v.Column + " = " + subSql + " "
} else {
sql += "SET " + v.Column + " = " + "? "
}
} else if i == 0 {
if v.SubqueryBuilder != nil {
sql += "SET " + v.Column + " = " + subSql + ", "
} else {
sql += "SET " + v.Column + " = " + "?, "
}
} else if i == len(qb.Setters)-1 {
if v.SubqueryBuilder != nil {
sql += v.Column + " = " + subSql + " "
} else {
sql += v.Column + " = " + "? "
}
} else {
if v.SubqueryBuilder != nil {
sql += v.Column + " = " + subSql + ", "
} else {
sql += v.Column + " = " + "?, "
}
}
}
// where filters
wSql, wParams, wErr := buildWhere[T](qb)
if wErr != nil {
return "", nil, wErr
}
params = append(params, wParams...)
sql += wSql
return sql, params, nil
}
func buildDelete[T any](qb *QueryBuilder) (string, []interface{}, error) {
sql := qb.BaseSQL
outSql, params, err := buildWhere[T](qb)
if err != nil {
return "", nil, err
}
outSql = sql + outSql
return outSql, params, nil
}
func validateSQLColName[T any](input string) bool {
// only match alphanumerics and underscores
// will still match if dot '.' present, but only if
// the dot is between two alphanumerics
valid := regexp.MustCompile(`^[A-Za-z0-9_]+(\.[A-Za-z0-9_]+)*$`)
if !valid.MatchString(input) {
return false
}
// Check if the column exists as a `db` note for the given type T
var t T
typeOfT := reflect.TypeOf(t)
for i := 0; i < typeOfT.NumField(); i++ {
field := typeOfT.Field(i)
if dbTag, ok := field.Tag.Lookup("db"); ok && dbTag == input {
return true
}
}
return false
}

View File

@@ -1,19 +1,18 @@
package database package database
import ( import (
"database/sql" "github.com/jmoiron/sqlx"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
) )
// The database package provides an interface between go code and a relational database. // The database package provides an interface between go code and a relational database.
var DB *sql.DB var DB *sqlx.DB
func Init() { func Init() {
var err error var err error
DB, err = sql.Open("sqlite3", "file:passwords.db") DB, err = sqlx.Connect("sqlite3", "file:passwords.db")
if err != nil { if err != nil {
panic(err.Error()) panic(err.Error())

25
go.mod
View File

@@ -2,40 +2,39 @@ module maxwarden
go 1.24.1 go 1.24.1
require golang.org/x/crypto v0.31.0
require ( require (
github.com/btcsuite/btcutil v1.0.2 github.com/btcsuite/btcutil v1.0.2
github.com/caarlos0/env/v11 v11.3.1 github.com/caarlos0/env/v11 v11.3.1
github.com/go-co-op/gocron/v2 v2.15.0 github.com/go-co-op/gocron/v2 v2.16.0
github.com/go-jet/jet/v2 v2.12.0 github.com/go-jet/jet/v2 v2.12.0
github.com/golang-migrate/migrate/v4 v4.18.2 github.com/golang-migrate/migrate/v4 v4.18.2
github.com/gomarkdown/markdown v0.0.0-20241105142532-d03b89096d81 github.com/gomarkdown/markdown v0.0.0-20250207164621-7a1f277a159e
github.com/google/uuid v1.6.0
github.com/jmoiron/sqlx v1.4.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/mattn/go-sqlite3 v1.14.24 github.com/mattn/go-sqlite3 v1.14.24
github.com/microcosm-cc/bluemonday v1.0.27 github.com/microcosm-cc/bluemonday v1.0.27
github.com/minio/highwayhash v1.0.3 github.com/minio/highwayhash v1.0.3
maragu.dev/gomponents v1.0.0 github.com/sethvargo/go-diceware v0.5.0
github.com/sethvargo/go-password v0.3.1
golang.org/x/crypto v0.36.0
maragu.dev/gomponents v1.1.0
maragu.dev/gomponents-htmx v0.6.1 maragu.dev/gomponents-htmx v0.6.1
) )
require ( require (
github.com/aymerick/douceur v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/css v1.0.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/jonboulle/clockwork v0.4.0 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect
github.com/kr/pretty v0.3.1 // indirect github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/sethvargo/go-diceware v0.5.0 // indirect
github.com/sethvargo/go-password v0.3.1 // indirect
github.com/stretchr/testify v1.10.0 // indirect github.com/stretchr/testify v1.10.0 // indirect
go.uber.org/atomic v1.7.0 // indirect go.uber.org/atomic v1.11.0 // indirect
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/net v0.37.0 // indirect
golang.org/x/net v0.33.0 // indirect golang.org/x/sys v0.31.0 // indirect
golang.org/x/sys v0.28.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

44
go.sum
View File

@@ -1,3 +1,5 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
@@ -15,19 +17,20 @@ github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5m
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U= github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-co-op/gocron/v2 v2.15.0 h1:Kpvo71VSihE+RImmpA+3ta5CcMhoRzMGw4dJawrj4zo= github.com/go-co-op/gocron/v2 v2.16.0 h1:uqUF6WFZ4enRU45pWFNcn1xpDLc+jBOTKhPQI16Z1xs=
github.com/go-co-op/gocron/v2 v2.15.0/go.mod h1:ZF70ZwEqz0OO4RBXE1sNxnANy/zvwLcattWEFsqpKig= github.com/go-co-op/gocron/v2 v2.16.0/go.mod h1:opexeOFy5BplhsKdA7bzY9zeYih8I8/WNJ4arTIFPVc=
github.com/go-jet/jet/v2 v2.12.0 h1:z2JfvBAZgsfxlQz6NXBYdZTXc7ep3jhbszTLtETv1JE= github.com/go-jet/jet/v2 v2.12.0 h1:z2JfvBAZgsfxlQz6NXBYdZTXc7ep3jhbszTLtETv1JE=
github.com/go-jet/jet/v2 v2.12.0/go.mod h1:ufQVRQeI1mbcO5R8uCEVcVf3Foej9kReBdwDx7YMWUM= github.com/go-jet/jet/v2 v2.12.0/go.mod h1:ufQVRQeI1mbcO5R8uCEVcVf3Foej9kReBdwDx7YMWUM=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/golang-migrate/migrate/v4 v4.18.2 h1:2VSCMz7x7mjyTXx3m2zPokOY82LTRgxK1yQYKo6wWQ8= github.com/golang-migrate/migrate/v4 v4.18.2 h1:2VSCMz7x7mjyTXx3m2zPokOY82LTRgxK1yQYKo6wWQ8=
github.com/golang-migrate/migrate/v4 v4.18.2/go.mod h1:2CM6tJvn2kqPXwnXO/d3rAQYiyoIm180VsO8PRX6Rpk= github.com/golang-migrate/migrate/v4 v4.18.2/go.mod h1:2CM6tJvn2kqPXwnXO/d3rAQYiyoIm180VsO8PRX6Rpk=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gomarkdown/markdown v0.0.0-20241105142532-d03b89096d81 h1:5lyLWsV+qCkoYqsKUDuycESh9DEIPVKN6iCFeL7ag50= github.com/gomarkdown/markdown v0.0.0-20250207164621-7a1f277a159e h1:ESHlT0RVZphh4JGBz49I5R6nTdC8Qyc08vU25GQHzzQ=
github.com/gomarkdown/markdown v0.0.0-20241105142532-d03b89096d81/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= github.com/gomarkdown/markdown v0.0.0-20250207164621-7a1f277a159e/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 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/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@@ -41,10 +44,12 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -53,6 +58,7 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
@@ -74,32 +80,28 @@ github.com/sethvargo/go-diceware v0.5.0 h1:exrQ7GpaBo00GqRVM1N8ChXSsi3oS7tjQiIeh
github.com/sethvargo/go-diceware v0.5.0/go.mod h1:Lg1SyPS7yQO6BBgTN5r4f2MUDkqGfLWsOjHPY0kA8iw= github.com/sethvargo/go-diceware v0.5.0/go.mod h1:Lg1SyPS7yQO6BBgTN5r4f2MUDkqGfLWsOjHPY0kA8iw=
github.com/sethvargo/go-password v0.3.1 h1:WqrLTjo7X6AcVYfC6R7GtSyuUQR9hGyAj/f1PYQZCJU= github.com/sethvargo/go-password v0.3.1 h1:WqrLTjo7X6AcVYfC6R7GtSyuUQR9hGyAj/f1PYQZCJU=
github.com/sethvargo/go-password v0.3.1/go.mod h1:rXofC1zT54N7R8K/h1WDUdkf9BOx5OptoxrMBcrXzvs= github.com/sethvargo/go-password v0.3.1/go.mod h1:rXofC1zT54N7R8K/h1WDUdkf9BOx5OptoxrMBcrXzvs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@@ -109,7 +111,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
maragu.dev/gomponents v1.0.0 h1:eeLScjq4PqP1l+r5z/GC+xXZhLHXa6RWUWGW7gSfLh4= maragu.dev/gomponents v1.1.0 h1:iCybZZChHr1eSlvkWp/JP3CrZGzctLudQ/JI3sBcO4U=
maragu.dev/gomponents v1.0.0/go.mod h1:oEDahza2gZoXDoDHhw8jBNgH+3UR5ni7Ur648HORydM= maragu.dev/gomponents v1.1.0/go.mod h1:oEDahza2gZoXDoDHhw8jBNgH+3UR5ni7Ur648HORydM=
maragu.dev/gomponents-htmx v0.6.1 h1:vXXOkvqEDKYxSwD1UwqmVp12YwFSuM6u8lsRn7Evyng= maragu.dev/gomponents-htmx v0.6.1 h1:vXXOkvqEDKYxSwD1UwqmVp12YwFSuM6u8lsRn7Evyng=
maragu.dev/gomponents-htmx v0.6.1/go.mod h1:51nXX+dTGff3usM7AJvbeOcQjzjpSycod+60CYeEP/M= maragu.dev/gomponents-htmx v0.6.1/go.mod h1:51nXX+dTGff3usM7AJvbeOcQjzjpSycod+60CYeEP/M=

View File

@@ -1,4 +1,4 @@
CREATE TABLE IF NOT EXISTS "user" ( CREATE TABLE IF NOT EXISTS "users" (
"id" INTEGER NOT NULL, "id" INTEGER NOT NULL,
"username" TEXT NOT NULL, "username" TEXT NOT NULL,
"email" TEXT NOT NULL, "email" TEXT NOT NULL,

View File

@@ -66,11 +66,6 @@ func CSSID(input string) string {
return "#" + input return "#" + input
} }
// For some reason this isn't included in the base distribution
func Template(children ...Node) Node {
return El("template", children...)
}
func Open() Node { func Open() Node {
return Attr("open") return Attr("open")
} }

View File

@@ -2,60 +2,55 @@ package users
import ( import (
"database/sql" "database/sql"
"maxwarden/.jet/model"
"maxwarden/database" "maxwarden/database"
. "maxwarden/.jet/table"
. "github.com/go-jet/jet/v2/sqlite"
) )
func FetchById(id int32) (model.User, error) { type User struct {
user := model.User{} ID int32 `db:"id"`
Username string `db:"username"`
stmt := SELECT( Email string `db:"email"`
User.AllColumns, Firstname string `db:"firstname"`
).FROM(User).WHERE( Lastname string `db:"lastname"`
User.ID.EQ(Int32(int32(id))), Password string `db:"password"`
) FailedAttempts int32 `db:"failed_attempts"`
SecurityStamp string `db:"security_stamp"`
err := stmt.Query(database.DB, &user) LastLogin string `db:"last_login"`
Data []byte `db:"data"`
return user, err
} }
func FetchByUsername(username string) (model.User, error) { func FetchById(id int32) (User, error) {
dest := model.User{} qb := &database.QueryBuilder{}
qb.BaseSQL = "SELECT * FROM users u WHERE u.id = ?"
stmt := SELECT( return database.Get[User](qb, database.DB, id)
User.AllColumns, }
).FROM(
User,
).WHERE(
User.Username.EQ(String(username)),
)
err := stmt.Query(database.DB, &dest) func FetchByUsername(username string) (User, error) {
return dest, err qb := &database.QueryBuilder{}
qb.BaseSQL = "SELECT * FROM users u WHERE u.username = ?"
return database.Get[User](qb, database.DB, username)
} }
func FetchSecurityStamp(userid int) (string, error) { func FetchSecurityStamp(userid int) (string, error) {
stamp := "" qb := &database.QueryBuilder{}
qb.BaseSQL = "SELECT u.security_stamp FROM users u WHERE u.id = ?"
stmt := SELECT( return database.Get[string](qb, database.DB, userid)
User.SecurityStamp,
).FROM(User).WHERE(
User.ID.EQ(Int32(int32(userid))),
)
err := stmt.Query(database.DB, &stamp)
return stamp, err
} }
func Update(user model.User) (sql.Result, error) { func Update(user User) (sql.Result, error) {
stmt := User.UPDATE(User.MutableColumns). qb := &database.QueryBuilder{}
MODEL(user). qb.BaseSQL = "UPDATE users"
WHERE(User.ID.EQ(Int32(user.ID)))
return stmt.Exec(database.DB) qb.Setters = []database.QuerySetter{
{Column: "failed_attempts", Parameter: user.FailedAttempts},
{Column: "data", Parameter: user.Data},
}
qb.Where = []database.QueryFilter{
{Column: "id", Parameter: user.ID},
}
return database.Update[User](qb, database.DB)
} }

View File

@@ -20,10 +20,9 @@ issues:
linters: linters:
enable: enable:
- bodyclose - bodyclose
- exportloopref - copyloopvar
- gofumpt - gofumpt
- goimports - goimports
- gosec
- gosimple - gosimple
- govet - govet
- ineffassign - ineffassign
@@ -39,6 +38,5 @@ output:
- format: colored-line-number - format: colored-line-number
print-issued-lines: true print-issued-lines: true
print-linter-name: true print-linter-name: true
uniq-by-line: true
path-prefix: "" path-prefix: ""
sort-results: true sort-results: true

View File

@@ -12,7 +12,7 @@ repos:
- id: end-of-file-fixer - id: end-of-file-fixer
- id: trailing-whitespace - id: trailing-whitespace
- repo: https://github.com/golangci/golangci-lint - repo: https://github.com/golangci/golangci-lint
rev: v1.61.0 rev: v1.64.5
hooks: hooks:
- id: golangci-lint - id: golangci-lint
- repo: https://github.com/TekWizely/pre-commit-golang - repo: https://github.com/TekWizely/pre-commit-golang

View File

@@ -16,7 +16,7 @@ test_coverage:
@go test -race -v $(GO_FLAGS) -count=1 -coverprofile=coverage.out -covermode=atomic $(GO_PKGS) @go test -race -v $(GO_FLAGS) -count=1 -coverprofile=coverage.out -covermode=atomic $(GO_PKGS)
test_ci: test_ci:
@TEST_ENV=ci go test -race -v $(GO_FLAGS) -count=1 $(GO_PKGS) @go test -race -v $(GO_FLAGS) -count=1 $(GO_PKGS)
mocks: mocks:
@go generate ./... @go generate ./...

View File

@@ -170,11 +170,20 @@ We appreciate the support for free and open source software!
This project is supported by: This project is supported by:
[Jetbrains](https://www.jetbrains.com/?from=gocron) [Jetbrains](https://www.jetbrains.com/?from=gocron)
![JetBrains logo](https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.png) ![JetBrains logo](https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.png)
[Sentry](https://sentry.io/welcome/) [Sentry](https://sentry.io/welcome/)
<p align="left">
<p align="left">
<a href="https://sentry.io/?utm_source=github&utm_medium=logo" target="_blank">
<img src="https://sentry-brand.storage.googleapis.com/sentry-wordmark-dark-280x84.png" alt="Sentry" width="280" height="84" />
</a>
</p>
</p>
## Star History ## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=go-co-op/gocron&type=Date)](https://star-history.com/#go-co-op/gocron&Date) [![Star History Chart](https://api.star-history.com/svg?repos=go-co-op/gocron&type=Date)](https://star-history.com/#go-co-op/gocron&Date)

View File

@@ -31,6 +31,9 @@ type executor struct {
// used to request jobs from the scheduler // used to request jobs from the scheduler
jobOutRequest chan jobOutRequest jobOutRequest chan jobOutRequest
// sends out job needs to update the next runs
jobUpdateNextRuns chan uuid.UUID
// used by the executor to receive a stop signal from the scheduler // used by the executor to receive a stop signal from the scheduler
stopCh chan struct{} stopCh chan struct{}
// the timeout value when stopping // the timeout value when stopping
@@ -247,6 +250,14 @@ func (e *executor) sendOutForRescheduling(jIn *jobIn) {
jIn.shouldSendOut = false jIn.shouldSendOut = false
} }
func (e *executor) sendOutForNextRunUpdate(jIn *jobIn) {
select {
case e.jobUpdateNextRuns <- jIn.id:
case <-e.ctx.Done():
return
}
}
func (e *executor) limitModeRunner(name string, in chan jobIn, wg *waitGroupWithMutex, limitMode LimitMode, rescheduleLimiter chan struct{}) { func (e *executor) limitModeRunner(name string, in chan jobIn, wg *waitGroupWithMutex, limitMode LimitMode, rescheduleLimiter chan struct{}) {
e.logger.Debug("gocron: limitModeRunner starting", "name", name) e.logger.Debug("gocron: limitModeRunner starting", "name", name)
for { for {
@@ -376,6 +387,7 @@ func (e *executor) runJob(j internalJob, jIn jobIn) {
_ = callJobFuncWithParams(j.afterLockError, j.id, j.name, err) _ = callJobFuncWithParams(j.afterLockError, j.id, j.name, err)
e.sendOutForRescheduling(&jIn) e.sendOutForRescheduling(&jIn)
e.incrementJobCounter(j, Skip) e.incrementJobCounter(j, Skip)
e.sendOutForNextRunUpdate(&jIn)
return return
} }
defer func() { _ = lock.Unlock(j.ctx) }() defer func() { _ = lock.Unlock(j.ctx) }()
@@ -385,6 +397,7 @@ func (e *executor) runJob(j internalJob, jIn jobIn) {
_ = callJobFuncWithParams(j.afterLockError, j.id, j.name, err) _ = callJobFuncWithParams(j.afterLockError, j.id, j.name, err)
e.sendOutForRescheduling(&jIn) e.sendOutForRescheduling(&jIn)
e.incrementJobCounter(j, Skip) e.incrementJobCounter(j, Skip)
e.sendOutForNextRunUpdate(&jIn)
return return
} }
defer func() { _ = lock.Unlock(j.ctx) }() defer func() { _ = lock.Unlock(j.ctx) }()

View File

@@ -6,13 +6,13 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/rand" "math/rand"
"slices"
"strings" "strings"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/jonboulle/clockwork" "github.com/jonboulle/clockwork"
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
"golang.org/x/exp/slices"
) )
// internalJob stores the information needed by the scheduler // internalJob stores the information needed by the scheduler
@@ -24,6 +24,7 @@ type internalJob struct {
id uuid.UUID id uuid.UUID
name string name string
tags []string tags []string
cron Cron
jobSchedule jobSchedule
// as some jobs may queue up, it's possible to // as some jobs may queue up, it's possible to
@@ -104,6 +105,20 @@ type limitRunsTo struct {
runCount uint runCount uint
} }
// -----------------------------------------------
// -----------------------------------------------
// --------------- Custom Cron -------------------
// -----------------------------------------------
// -----------------------------------------------
// Cron defines the interface that must be
// implemented to provide a custom cron implementation for
// the job. Pass in the implementation using the JobOption WithCronImplementation.
type Cron interface {
IsValid(crontab string, location *time.Location, now time.Time) error
Next(lastRun time.Time) time.Time
}
// ----------------------------------------------- // -----------------------------------------------
// ----------------------------------------------- // -----------------------------------------------
// --------------- Job Variants ------------------ // --------------- Job Variants ------------------
@@ -116,21 +131,29 @@ type JobDefinition interface {
setup(j *internalJob, l *time.Location, now time.Time) error setup(j *internalJob, l *time.Location, now time.Time) error
} }
var _ JobDefinition = (*cronJobDefinition)(nil) // Default cron implementation
type cronJobDefinition struct { func newDefaultCronImplementation(withSeconds bool) Cron {
crontab string return &defaultCron{
withSeconds: withSeconds,
}
}
var _ Cron = (*defaultCron)(nil)
type defaultCron struct {
cronSchedule cron.Schedule
withSeconds bool withSeconds bool
} }
func (c cronJobDefinition) setup(j *internalJob, location *time.Location, now time.Time) error { func (c *defaultCron) IsValid(crontab string, location *time.Location, now time.Time) error {
var withLocation string var withLocation string
if strings.HasPrefix(c.crontab, "TZ=") || strings.HasPrefix(c.crontab, "CRON_TZ=") { if strings.HasPrefix(crontab, "TZ=") || strings.HasPrefix(crontab, "CRON_TZ=") {
withLocation = c.crontab withLocation = crontab
} else { } else {
// since the user didn't provide a timezone default to the location // since the user didn't provide a timezone default to the location
// passed in by the scheduler. Default: time.Local // passed in by the scheduler. Default: time.Local
withLocation = fmt.Sprintf("CRON_TZ=%s %s", location.String(), c.crontab) withLocation = fmt.Sprintf("CRON_TZ=%s %s", location.String(), crontab)
} }
var ( var (
@@ -150,8 +173,32 @@ func (c cronJobDefinition) setup(j *internalJob, location *time.Location, now ti
if cronSchedule.Next(now).IsZero() { if cronSchedule.Next(now).IsZero() {
return ErrCronJobInvalid return ErrCronJobInvalid
} }
c.cronSchedule = cronSchedule
return nil
}
j.jobSchedule = &cronJob{cronSchedule: cronSchedule} func (c *defaultCron) Next(lastRun time.Time) time.Time {
return c.cronSchedule.Next(lastRun)
}
// default cron job implementation
var _ JobDefinition = (*cronJobDefinition)(nil)
type cronJobDefinition struct {
crontab string
cron Cron
}
func (c cronJobDefinition) setup(j *internalJob, location *time.Location, now time.Time) error {
if j.cron != nil {
c.cron = j.cron
}
if err := c.cron.IsValid(c.crontab, location, now); err != nil {
return err
}
j.jobSchedule = &cronJob{crontab: c.crontab, cronSchedule: c.cron}
return nil return nil
} }
@@ -164,7 +211,7 @@ func (c cronJobDefinition) setup(j *internalJob, location *time.Location, now ti
func CronJob(crontab string, withSeconds bool) JobDefinition { func CronJob(crontab string, withSeconds bool) JobDefinition {
return cronJobDefinition{ return cronJobDefinition{
crontab: crontab, crontab: crontab,
withSeconds: withSeconds, cron: newDefaultCronImplementation(withSeconds),
} }
} }
@@ -369,11 +416,9 @@ func (m monthlyJobDefinition) setup(j *internalJob, location *time.Location, _ t
} }
} }
daysStart = removeSliceDuplicatesInt(daysStart) daysStart = removeSliceDuplicatesInt(daysStart)
slices.Sort(daysStart)
ms.days = daysStart ms.days = daysStart
daysEnd = removeSliceDuplicatesInt(daysEnd) daysEnd = removeSliceDuplicatesInt(daysEnd)
slices.Sort(daysEnd)
ms.daysFromEnd = daysEnd ms.daysFromEnd = daysEnd
atTimesDate, err := convertAtTimesToDateTime(m.atTimes, location) atTimesDate, err := convertAtTimesToDateTime(m.atTimes, location)
@@ -610,6 +655,15 @@ func WithName(name string) JobOption {
} }
} }
// WithCronImplementation sets the custom Cron implementation for the job.
// This is only utilized for the CronJob type.
func WithCronImplementation(c Cron) JobOption {
return func(j *internalJob, _ time.Time) error {
j.cron = c
return nil
}
}
// WithSingletonMode keeps the job from running again if it is already running. // WithSingletonMode keeps the job from running again if it is already running.
// This is useful for jobs that should not overlap, and that occasionally // This is useful for jobs that should not overlap, and that occasionally
// (but not consistently) run longer than the interval between job runs. // (but not consistently) run longer than the interval between job runs.
@@ -820,7 +874,8 @@ type jobSchedule interface {
var _ jobSchedule = (*cronJob)(nil) var _ jobSchedule = (*cronJob)(nil)
type cronJob struct { type cronJob struct {
cronSchedule cron.Schedule crontab string
cronSchedule Cron
} }
func (j *cronJob) next(lastRun time.Time) time.Time { func (j *cronJob) next(lastRun time.Time) time.Time {
@@ -864,7 +919,7 @@ func (d dailyJob) next(lastRun time.Time) time.Time {
} }
firstPass = false firstPass = false
startNextDay := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day()+int(d.interval), 0, 0, 0, lastRun.Nanosecond(), lastRun.Location()) startNextDay := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day()+int(d.interval), 0, 0, 0, 0, lastRun.Location())
return d.nextDay(startNextDay, firstPass) return d.nextDay(startNextDay, firstPass)
} }
@@ -872,7 +927,7 @@ func (d dailyJob) nextDay(lastRun time.Time, firstPass bool) time.Time {
for _, at := range d.atTimes { for _, at := range d.atTimes {
// sub the at time hour/min/sec onto the lastScheduledRun's values // sub the at time hour/min/sec onto the lastScheduledRun's values
// to use in checks to see if we've got our next run time // to use in checks to see if we've got our next run time
atDate := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day(), at.Hour(), at.Minute(), at.Second(), lastRun.Nanosecond(), lastRun.Location()) atDate := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day(), at.Hour(), at.Minute(), at.Second(), 0, lastRun.Location())
if firstPass && atDate.After(lastRun) { if firstPass && atDate.After(lastRun) {
// checking to see if it is after i.e. greater than, // checking to see if it is after i.e. greater than,
@@ -918,7 +973,7 @@ func (w weeklyJob) nextWeekDayAtTime(lastRun time.Time, firstPass bool) time.Tim
for _, at := range w.atTimes { for _, at := range w.atTimes {
// sub the at time hour/min/sec onto the lastScheduledRun's values // sub the at time hour/min/sec onto the lastScheduledRun's values
// to use in checks to see if we've got our next run time // to use in checks to see if we've got our next run time
atDate := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day()+int(weekDayDiff), at.Hour(), at.Minute(), at.Second(), lastRun.Nanosecond(), lastRun.Location()) atDate := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day()+int(weekDayDiff), at.Hour(), at.Minute(), at.Second(), 0, lastRun.Location())
if firstPass && atDate.After(lastRun) { if firstPass && atDate.After(lastRun) {
// checking to see if it is after i.e. greater than, // checking to see if it is after i.e. greater than,
@@ -986,7 +1041,7 @@ func (m monthlyJob) nextMonthDayAtTime(lastRun time.Time, days []int, firstPass
for _, at := range m.atTimes { for _, at := range m.atTimes {
// sub the day, and the at time hour/min/sec onto the lastScheduledRun's values // sub the day, and the at time hour/min/sec onto the lastScheduledRun's values
// to use in checks to see if we've got our next run time // to use in checks to see if we've got our next run time
atDate := time.Date(lastRun.Year(), lastRun.Month(), day, at.Hour(), at.Minute(), at.Second(), lastRun.Nanosecond(), lastRun.Location()) atDate := time.Date(lastRun.Year(), lastRun.Month(), day, at.Hour(), at.Minute(), at.Second(), 0, lastRun.Location())
if atDate.Month() != lastRun.Month() { if atDate.Month() != lastRun.Month() {
// this check handles if we're setting a day not in the current month // this check handles if we're setting a day not in the current month

View File

@@ -5,11 +5,12 @@ import (
"context" "context"
"reflect" "reflect"
"runtime" "runtime"
"slices"
"strings"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/jonboulle/clockwork" "github.com/jonboulle/clockwork"
"golang.org/x/exp/slices"
) )
var _ Scheduler = (*scheduler)(nil) var _ Scheduler = (*scheduler)(nil)
@@ -137,6 +138,7 @@ func NewScheduler(options ...SchedulerOption) (Scheduler, error) {
jobsIn: make(chan jobIn), jobsIn: make(chan jobIn),
jobsOutForRescheduling: make(chan uuid.UUID), jobsOutForRescheduling: make(chan uuid.UUID),
jobUpdateNextRuns: make(chan uuid.UUID),
jobsOutCompleted: make(chan uuid.UUID), jobsOutCompleted: make(chan uuid.UUID),
jobOutRequest: make(chan jobOutRequest, 1000), jobOutRequest: make(chan jobOutRequest, 1000),
done: make(chan error), done: make(chan error),
@@ -175,7 +177,8 @@ func NewScheduler(options ...SchedulerOption) (Scheduler, error) {
select { select {
case id := <-s.exec.jobsOutForRescheduling: case id := <-s.exec.jobsOutForRescheduling:
s.selectExecJobsOutForRescheduling(id) s.selectExecJobsOutForRescheduling(id)
case id := <-s.exec.jobUpdateNextRuns:
s.updateNextScheduled(id)
case id := <-s.exec.jobsOutCompleted: case id := <-s.exec.jobsOutCompleted:
s.selectExecJobsOutCompleted(id) s.selectExecJobsOutCompleted(id)
@@ -237,11 +240,8 @@ func (s *scheduler) stopScheduler() {
for _, j := range s.jobs { for _, j := range s.jobs {
j.stop() j.stop()
} }
for id, j := range s.jobs { for _, j := range s.jobs {
<-j.ctx.Done() <-j.ctx.Done()
j.ctx, j.cancel = context.WithCancel(s.shutdownCtx)
s.jobs[id] = j
} }
var err error var err error
if s.started { if s.started {
@@ -253,6 +253,21 @@ func (s *scheduler) stopScheduler() {
err = ErrStopExecutorTimedOut err = ErrStopExecutorTimedOut
} }
} }
for id, j := range s.jobs {
oldCtx := j.ctx
if j.parentCtx == nil {
j.parentCtx = s.shutdownCtx
}
j.ctx, j.cancel = context.WithCancel(j.parentCtx)
// also replace the old context with the new one in the parameters
if len(j.parameters) > 0 && j.parameters[0] == oldCtx {
j.parameters[0] = j.ctx
}
s.jobs[id] = j
}
s.stopErrCh <- err s.stopErrCh <- err
s.started = false s.started = false
s.logger.Debug("gocron: scheduler stopped") s.logger.Debug("gocron: scheduler stopped")
@@ -267,14 +282,7 @@ func (s *scheduler) selectAllJobsOutRequest(out allJobsOutRequest) {
} }
slices.SortFunc(outJobs, func(a, b Job) int { slices.SortFunc(outJobs, func(a, b Job) int {
aID, bID := a.ID().String(), b.ID().String() aID, bID := a.ID().String(), b.ID().String()
switch { return strings.Compare(aID, bID)
case aID < bID:
return -1
case aID > bID:
return 1
default:
return 0
}
}) })
select { select {
case <-s.shutdownCtx.Done(): case <-s.shutdownCtx.Done():
@@ -335,7 +343,7 @@ func (s *scheduler) selectExecJobsOutForRescheduling(id uuid.UUID) {
return return
} }
scheduleFrom := j.lastRun var scheduleFrom time.Time
if len(j.nextScheduled) > 0 { if len(j.nextScheduled) > 0 {
// always grab the last element in the slice as that is the furthest // always grab the last element in the slice as that is the furthest
// out in the future and the time from which we want to calculate // out in the future and the time from which we want to calculate
@@ -366,6 +374,15 @@ func (s *scheduler) selectExecJobsOutForRescheduling(id uuid.UUID) {
} }
} }
if slices.Contains(j.nextScheduled, next) {
// if the next value is a duplicate of what's already in the nextScheduled slice, for example:
// - the job is being rescheduled off the same next run value as before
// increment to the next, next value
for slices.Contains(j.nextScheduled, next) {
next = j.next(next)
}
}
// Clean up any existing timer to prevent leaks // Clean up any existing timer to prevent leaks
if j.timer != nil { if j.timer != nil {
j.timer.Stop() j.timer.Stop()
@@ -390,6 +407,22 @@ func (s *scheduler) selectExecJobsOutForRescheduling(id uuid.UUID) {
s.jobs[id] = j s.jobs[id] = j
} }
func (s *scheduler) updateNextScheduled(id uuid.UUID) {
j, ok := s.jobs[id]
if !ok {
return
}
var newNextScheduled []time.Time
for _, t := range j.nextScheduled {
if t.Before(s.now()) {
continue
}
newNextScheduled = append(newNextScheduled, t)
}
j.nextScheduled = newNextScheduled
s.jobs[id] = j
}
func (s *scheduler) selectExecJobsOutCompleted(id uuid.UUID) { func (s *scheduler) selectExecJobsOutCompleted(id uuid.UUID) {
j, ok := s.jobs[id] j, ok := s.jobs[id]
if !ok { if !ok {

View File

@@ -3,12 +3,11 @@ package gocron
import ( import (
"context" "context"
"reflect" "reflect"
"slices"
"sync" "sync"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
) )
func callJobFuncWithParams(jobFunc any, params ...any) error { func callJobFuncWithParams(jobFunc any, params ...any) error {
@@ -63,12 +62,8 @@ func requestJobCtx(ctx context.Context, id uuid.UUID, ch chan jobOutRequest) *in
} }
func removeSliceDuplicatesInt(in []int) []int { func removeSliceDuplicatesInt(in []int) []int {
m := make(map[int]struct{}) slices.Sort(in)
return slices.Compact(in)
for _, i := range in {
m[i] = struct{}{}
}
return maps.Keys(m)
} }
func convertAtTimesToDateTime(atTimes AtTimes, location *time.Location) ([]time.Time, error) { func convertAtTimesToDateTime(atTimes AtTimes, location *time.Location) ([]time.Time, error) {

View File

@@ -951,8 +951,6 @@ func (p *Parser) fencedCodeBlock(data []byte, doRender bool) int {
work.WriteByte('\n') work.WriteByte('\n')
for { for {
// safe to assume beg < len(data)
// check for the end of the code block // check for the end of the code block
fenceEnd, _ := isFenceLine(data[beg:], nil, marker) fenceEnd, _ := isFenceLine(data[beg:], nil, marker)
if fenceEnd != 0 { if fenceEnd != 0 {
@@ -969,13 +967,13 @@ func (p *Parser) fencedCodeBlock(data []byte, doRender bool) int {
} }
// verbatim copy to the working buffer // verbatim copy to the working buffer
if doRender {
work.Write(data[beg:end]) work.Write(data[beg:end])
}
beg = end beg = end
} }
if doRender { if !doRender {
return beg
}
codeBlock := &ast.CodeBlock{ codeBlock := &ast.CodeBlock{
IsFenced: true, IsFenced: true,
} }
@@ -1009,7 +1007,6 @@ func (p *Parser) fencedCodeBlock(data []byte, doRender bool) int {
// Still here, normal block // Still here, normal block
p.AddBlock(codeBlock) p.AddBlock(codeBlock)
finalizeCodeBlock(codeBlock) finalizeCodeBlock(codeBlock)
}
return beg return beg
} }
@@ -1353,6 +1350,7 @@ func finalizeList(list *ast.List) {
// Parse a single list item. // Parse a single list item.
// Assumes initial prefix is already removed if this is a sublist. // Assumes initial prefix is already removed if this is a sublist.
func (p *Parser) listItem(data []byte, flags *ast.ListType) int { func (p *Parser) listItem(data []byte, flags *ast.ListType) int {
isDefinitionList := *flags&ast.ListTypeDefinition != 0
// keep track of the indentation of the first line // keep track of the indentation of the first line
itemIndent := 0 itemIndent := 0
if data[0] == '\t' { if data[0] == '\t' {
@@ -1385,7 +1383,7 @@ func (p *Parser) listItem(data []byte, flags *ast.ListType) int {
} }
if i == 0 { if i == 0 {
// if in definition list, set term flag and continue // if in definition list, set term flag and continue
if *flags&ast.ListTypeDefinition != 0 { if isDefinitionList {
*flags |= ast.ListTypeTerm *flags |= ast.ListTypeTerm
} else { } else {
return 0 return 0
@@ -1446,7 +1444,14 @@ gatherlines:
// If there is a fence line (marking starting of a code block) // If there is a fence line (marking starting of a code block)
// without indent do not process it as part of the list. // without indent do not process it as part of the list.
if p.extensions&FencedCode != 0 { //
// does not apply for definition lists because it causes infinite
// loop if text before defintion term is fenced code block start
// marker but not part of actual fenced code block
// for defnition lists we're called after parsing fence code blocks
// so we kno this cannot be a fenced block
// https://github.com/gomarkdown/markdown/issues/326
if !isDefinitionList && p.extensions&FencedCode != 0 {
fenceLineEnd, _ := isFenceLine(chunk, nil, "") fenceLineEnd, _ := isFenceLine(chunk, nil, "")
if fenceLineEnd > 0 && indent == 0 { if fenceLineEnd > 0 && indent == 0 {
*flags |= ast.ListItemEndOfList *flags |= ast.ListItemEndOfList

View File

@@ -271,7 +271,7 @@ func maybeInlineFootnoteOrSuper(p *Parser, data []byte, offset int) (int, ast.No
// '[': parse a link or an image or a footnote or a citation // '[': parse a link or an image or a footnote or a citation
func link(p *Parser, data []byte, offset int) (int, ast.Node) { func link(p *Parser, data []byte, offset int) (int, ast.Node) {
// no links allowed inside regular links, footnote, and deferred footnotes // no links allowed inside regular links, footnote, and deferred footnotes
if p.insideLink && (offset > 0 && data[offset-1] == '[' || len(data)-1 > offset && data[offset+1] == '^') { if p.InsideLink && (offset > 0 && data[offset-1] == '[' || len(data)-1 > offset && data[offset+1] == '^') {
return 0, nil return 0, nil
} }
@@ -362,25 +362,27 @@ func link(p *Parser, data []byte, offset int) (int, ast.Node) {
linkB := i linkB := i
brace := 0 brace := 0
var c byte
// look for link end: ' " ) // look for link end: ' " )
findlinkend: findlinkend:
for i < len(data) { for i < len(data) {
c = data[i]
switch { switch {
case data[i] == '\\': case c == '\\':
i += 2 i += 2
case data[i] == '(': case c == '(':
brace++ brace++
i++ i++
case data[i] == ')': case c == ')':
if brace <= 0 { if brace <= 0 {
break findlinkend break findlinkend
} }
brace-- brace--
i++ i++
case data[i] == '\'' || data[i] == '"': case c == '\'' || c == '"':
break findlinkend break findlinkend
default: default:
@@ -402,14 +404,15 @@ func link(p *Parser, data []byte, offset int) (int, ast.Node) {
findtitleend: findtitleend:
for i < len(data) { for i < len(data) {
c = data[i]
switch { switch {
case data[i] == '\\': case c == '\\':
i++ i++
case data[i] == data[titleB-1]: // matching title delimiter case c == data[titleB-1]: // matching title delimiter
titleEndCharFound = true titleEndCharFound = true
case titleEndCharFound && data[i] == ')': case titleEndCharFound && c == ')':
break findtitleend break findtitleend
} }
i++ i++
@@ -619,10 +622,10 @@ func link(p *Parser, data []byte, offset int) (int, ast.Node) {
} else { } else {
// links cannot contain other links, so turn off link parsing // links cannot contain other links, so turn off link parsing
// temporarily and recurse // temporarily and recurse
insideLink := p.insideLink InsideLink := p.InsideLink
p.insideLink = true p.InsideLink = true
p.Inline(link, data[1:txtE]) p.Inline(link, data[1:txtE])
p.insideLink = insideLink p.InsideLink = InsideLink
} }
return i, link return i, link
@@ -857,7 +860,7 @@ const shortestPrefix = 6 // len("ftp://"), the shortest of the above
func maybeAutoLink(p *Parser, data []byte, offset int) (int, ast.Node) { func maybeAutoLink(p *Parser, data []byte, offset int) (int, ast.Node) {
// quick check to rule out most false hits // quick check to rule out most false hits
if p.insideLink || len(data) < offset+shortestPrefix { if p.InsideLink || len(data) < offset+shortestPrefix {
return 0, nil return 0, nil
} }
for _, prefix := range protocolPrefixes { for _, prefix := range protocolPrefixes {

View File

@@ -103,7 +103,7 @@ type Parser struct {
inlineCallback [256]InlineParser inlineCallback [256]InlineParser
nesting int nesting int
maxNesting int maxNesting int
insideLink bool InsideLink bool
indexCnt int // incremented after every index indexCnt int // incremented after every index
// Footnotes need to be ordered as well as available to quickly check for // Footnotes need to be ordered as well as available to quickly check for
@@ -143,7 +143,7 @@ func NewWithExtensions(extension Extensions) *Parser {
refs: make(map[string]*reference), refs: make(map[string]*reference),
refsRecord: make(map[string]struct{}), refsRecord: make(map[string]struct{}),
maxNesting: 64, maxNesting: 64,
insideLink: false, InsideLink: false,
Doc: &ast.Document{}, Doc: &ast.Document{},
extensions: extension, extensions: extension,
allClosed: true, allClosed: true,

25
vendor/github.com/jmoiron/sqlx/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,25 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
.idea
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
tags
environ

23
vendor/github.com/jmoiron/sqlx/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,23 @@
Copyright (c) 2013, Jason Moiron
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.

30
vendor/github.com/jmoiron/sqlx/Makefile generated vendored Normal file
View File

@@ -0,0 +1,30 @@
.ONESHELL:
SHELL = /bin/sh
.SHELLFLAGS = -ec
BASE_PACKAGE := github.com/jmoiron/sqlx
tooling:
go install honnef.co/go/tools/cmd/staticcheck@v0.4.7
go install golang.org/x/vuln/cmd/govulncheck@v1.0.4
go install golang.org/x/tools/cmd/goimports@v0.20.0
has-changes:
git diff --exit-code --quiet HEAD --
lint:
go vet ./...
staticcheck -checks=all ./...
fmt:
go list -f '{{.Dir}}' ./... | xargs -I {} goimports -local $(BASE_PACKAGE) -w {}
vuln-check:
govulncheck ./...
test-race:
go test -v -race -count=1 ./...
update-dependencies:
go get -u -t -v ./...
go mod tidy

213
vendor/github.com/jmoiron/sqlx/README.md generated vendored Normal file
View File

@@ -0,0 +1,213 @@
# sqlx
[![CircleCI](https://dl.circleci.com/status-badge/img/gh/jmoiron/sqlx/tree/master.svg?style=shield)](https://dl.circleci.com/status-badge/redirect/gh/jmoiron/sqlx/tree/master) [![Coverage Status](https://coveralls.io/repos/github/jmoiron/sqlx/badge.svg?branch=master)](https://coveralls.io/github/jmoiron/sqlx?branch=master) [![Godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/jmoiron/sqlx) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/jmoiron/sqlx/master/LICENSE)
sqlx is a library which provides a set of extensions on go's standard
`database/sql` library. The sqlx versions of `sql.DB`, `sql.TX`, `sql.Stmt`,
et al. all leave the underlying interfaces untouched, so that their interfaces
are a superset on the standard ones. This makes it relatively painless to
integrate existing codebases using database/sql with sqlx.
Major additional concepts are:
* Marshal rows into structs (with embedded struct support), maps, and slices
* Named parameter support including prepared statements
* `Get` and `Select` to go quickly from query to struct/slice
In addition to the [godoc API documentation](http://godoc.org/github.com/jmoiron/sqlx),
there is also some [user documentation](http://jmoiron.github.io/sqlx/) that
explains how to use `database/sql` along with sqlx.
## Recent Changes
1.3.0:
* `sqlx.DB.Connx(context.Context) *sqlx.Conn`
* `sqlx.BindDriver(driverName, bindType)`
* support for `[]map[string]interface{}` to do "batch" insertions
* allocation & perf improvements for `sqlx.In`
DB.Connx returns an `sqlx.Conn`, which is an `sql.Conn`-alike consistent with
sqlx's wrapping of other types.
`BindDriver` allows users to control the bindvars that sqlx will use for drivers,
and add new drivers at runtime. This results in a very slight performance hit
when resolving the driver into a bind type (~40ns per call), but it allows users
to specify what bindtype their driver uses even when sqlx has not been updated
to know about it by default.
### Backwards Compatibility
Compatibility with the most recent two versions of Go is a requirement for any
new changes. Compatibility beyond that is not guaranteed.
Versioning is done with Go modules. Breaking changes (eg. removing deprecated API)
will get major version number bumps.
## install
go get github.com/jmoiron/sqlx
## issues
Row headers can be ambiguous (`SELECT 1 AS a, 2 AS a`), and the result of
`Columns()` does not fully qualify column names in queries like:
```sql
SELECT a.id, a.name, b.id, b.name FROM foos AS a JOIN foos AS b ON a.parent = b.id;
```
making a struct or map destination ambiguous. Use `AS` in your queries
to give columns distinct names, `rows.Scan` to scan them manually, or
`SliceScan` to get a slice of results.
## usage
Below is an example which shows some common use cases for sqlx. Check
[sqlx_test.go](https://github.com/jmoiron/sqlx/blob/master/sqlx_test.go) for more
usage.
```go
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/lib/pq"
"github.com/jmoiron/sqlx"
)
var schema = `
CREATE TABLE person (
first_name text,
last_name text,
email text
);
CREATE TABLE place (
country text,
city text NULL,
telcode integer
)`
type Person struct {
FirstName string `db:"first_name"`
LastName string `db:"last_name"`
Email string
}
type Place struct {
Country string
City sql.NullString
TelCode int
}
func main() {
// this Pings the database trying to connect
// use sqlx.Open() for sql.Open() semantics
db, err := sqlx.Connect("postgres", "user=foo dbname=bar sslmode=disable")
if err != nil {
log.Fatalln(err)
}
// exec the schema or fail; multi-statement Exec behavior varies between
// database drivers; pq will exec them all, sqlite3 won't, ymmv
db.MustExec(schema)
tx := db.MustBegin()
tx.MustExec("INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3)", "Jason", "Moiron", "jmoiron@jmoiron.net")
tx.MustExec("INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3)", "John", "Doe", "johndoeDNE@gmail.net")
tx.MustExec("INSERT INTO place (country, city, telcode) VALUES ($1, $2, $3)", "United States", "New York", "1")
tx.MustExec("INSERT INTO place (country, telcode) VALUES ($1, $2)", "Hong Kong", "852")
tx.MustExec("INSERT INTO place (country, telcode) VALUES ($1, $2)", "Singapore", "65")
// Named queries can use structs, so if you have an existing struct (i.e. person := &Person{}) that you have populated, you can pass it in as &person
tx.NamedExec("INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)", &Person{"Jane", "Citizen", "jane.citzen@example.com"})
tx.Commit()
// Query the database, storing results in a []Person (wrapped in []interface{})
people := []Person{}
db.Select(&people, "SELECT * FROM person ORDER BY first_name ASC")
jason, john := people[0], people[1]
fmt.Printf("%#v\n%#v", jason, john)
// Person{FirstName:"Jason", LastName:"Moiron", Email:"jmoiron@jmoiron.net"}
// Person{FirstName:"John", LastName:"Doe", Email:"johndoeDNE@gmail.net"}
// You can also get a single result, a la QueryRow
jason = Person{}
err = db.Get(&jason, "SELECT * FROM person WHERE first_name=$1", "Jason")
fmt.Printf("%#v\n", jason)
// Person{FirstName:"Jason", LastName:"Moiron", Email:"jmoiron@jmoiron.net"}
// if you have null fields and use SELECT *, you must use sql.Null* in your struct
places := []Place{}
err = db.Select(&places, "SELECT * FROM place ORDER BY telcode ASC")
if err != nil {
fmt.Println(err)
return
}
usa, singsing, honkers := places[0], places[1], places[2]
fmt.Printf("%#v\n%#v\n%#v\n", usa, singsing, honkers)
// Place{Country:"United States", City:sql.NullString{String:"New York", Valid:true}, TelCode:1}
// Place{Country:"Singapore", City:sql.NullString{String:"", Valid:false}, TelCode:65}
// Place{Country:"Hong Kong", City:sql.NullString{String:"", Valid:false}, TelCode:852}
// Loop through rows using only one struct
place := Place{}
rows, err := db.Queryx("SELECT * FROM place")
for rows.Next() {
err := rows.StructScan(&place)
if err != nil {
log.Fatalln(err)
}
fmt.Printf("%#v\n", place)
}
// Place{Country:"United States", City:sql.NullString{String:"New York", Valid:true}, TelCode:1}
// Place{Country:"Hong Kong", City:sql.NullString{String:"", Valid:false}, TelCode:852}
// Place{Country:"Singapore", City:sql.NullString{String:"", Valid:false}, TelCode:65}
// Named queries, using `:name` as the bindvar. Automatic bindvar support
// which takes into account the dbtype based on the driverName on sqlx.Open/Connect
_, err = db.NamedExec(`INSERT INTO person (first_name,last_name,email) VALUES (:first,:last,:email)`,
map[string]interface{}{
"first": "Bin",
"last": "Smuth",
"email": "bensmith@allblacks.nz",
})
// Selects Mr. Smith from the database
rows, err = db.NamedQuery(`SELECT * FROM person WHERE first_name=:fn`, map[string]interface{}{"fn": "Bin"})
// Named queries can also use structs. Their bind names follow the same rules
// as the name -> db mapping, so struct fields are lowercased and the `db` tag
// is taken into consideration.
rows, err = db.NamedQuery(`SELECT * FROM person WHERE first_name=:first_name`, jason)
// batch insert
// batch insert with structs
personStructs := []Person{
{FirstName: "Ardie", LastName: "Savea", Email: "asavea@ab.co.nz"},
{FirstName: "Sonny Bill", LastName: "Williams", Email: "sbw@ab.co.nz"},
{FirstName: "Ngani", LastName: "Laumape", Email: "nlaumape@ab.co.nz"},
}
_, err = db.NamedExec(`INSERT INTO person (first_name, last_name, email)
VALUES (:first_name, :last_name, :email)`, personStructs)
// batch insert with maps
personMaps := []map[string]interface{}{
{"first_name": "Ardie", "last_name": "Savea", "email": "asavea@ab.co.nz"},
{"first_name": "Sonny Bill", "last_name": "Williams", "email": "sbw@ab.co.nz"},
{"first_name": "Ngani", "last_name": "Laumape", "email": "nlaumape@ab.co.nz"},
}
_, err = db.NamedExec(`INSERT INTO person (first_name, last_name, email)
VALUES (:first_name, :last_name, :email)`, personMaps)
}
```

265
vendor/github.com/jmoiron/sqlx/bind.go generated vendored Normal file
View File

@@ -0,0 +1,265 @@
package sqlx
import (
"bytes"
"database/sql/driver"
"errors"
"reflect"
"strconv"
"strings"
"sync"
"github.com/jmoiron/sqlx/reflectx"
)
// Bindvar types supported by Rebind, BindMap and BindStruct.
const (
UNKNOWN = iota
QUESTION
DOLLAR
NAMED
AT
)
var defaultBinds = map[int][]string{
DOLLAR: []string{"postgres", "pgx", "pq-timeouts", "cloudsqlpostgres", "ql", "nrpostgres", "cockroach"},
QUESTION: []string{"mysql", "sqlite3", "nrmysql", "nrsqlite3"},
NAMED: []string{"oci8", "ora", "goracle", "godror"},
AT: []string{"sqlserver"},
}
var binds sync.Map
func init() {
for bind, drivers := range defaultBinds {
for _, driver := range drivers {
BindDriver(driver, bind)
}
}
}
// BindType returns the bindtype for a given database given a drivername.
func BindType(driverName string) int {
itype, ok := binds.Load(driverName)
if !ok {
return UNKNOWN
}
return itype.(int)
}
// BindDriver sets the BindType for driverName to bindType.
func BindDriver(driverName string, bindType int) {
binds.Store(driverName, bindType)
}
// FIXME: this should be able to be tolerant of escaped ?'s in queries without
// losing much speed, and should be to avoid confusion.
// Rebind a query from the default bindtype (QUESTION) to the target bindtype.
func Rebind(bindType int, query string) string {
switch bindType {
case QUESTION, UNKNOWN:
return query
}
// Add space enough for 10 params before we have to allocate
rqb := make([]byte, 0, len(query)+10)
var i, j int
for i = strings.Index(query, "?"); i != -1; i = strings.Index(query, "?") {
rqb = append(rqb, query[:i]...)
switch bindType {
case DOLLAR:
rqb = append(rqb, '$')
case NAMED:
rqb = append(rqb, ':', 'a', 'r', 'g')
case AT:
rqb = append(rqb, '@', 'p')
}
j++
rqb = strconv.AppendInt(rqb, int64(j), 10)
query = query[i+1:]
}
return string(append(rqb, query...))
}
// Experimental implementation of Rebind which uses a bytes.Buffer. The code is
// much simpler and should be more resistant to odd unicode, but it is twice as
// slow. Kept here for benchmarking purposes and to possibly replace Rebind if
// problems arise with its somewhat naive handling of unicode.
func rebindBuff(bindType int, query string) string {
if bindType != DOLLAR {
return query
}
b := make([]byte, 0, len(query))
rqb := bytes.NewBuffer(b)
j := 1
for _, r := range query {
if r == '?' {
rqb.WriteRune('$')
rqb.WriteString(strconv.Itoa(j))
j++
} else {
rqb.WriteRune(r)
}
}
return rqb.String()
}
func asSliceForIn(i interface{}) (v reflect.Value, ok bool) {
if i == nil {
return reflect.Value{}, false
}
v = reflect.ValueOf(i)
t := reflectx.Deref(v.Type())
// Only expand slices
if t.Kind() != reflect.Slice {
return reflect.Value{}, false
}
// []byte is a driver.Value type so it should not be expanded
if t == reflect.TypeOf([]byte{}) {
return reflect.Value{}, false
}
return v, true
}
// In expands slice values in args, returning the modified query string
// and a new arg list that can be executed by a database. The `query` should
// use the `?` bindVar. The return value uses the `?` bindVar.
func In(query string, args ...interface{}) (string, []interface{}, error) {
// argMeta stores reflect.Value and length for slices and
// the value itself for non-slice arguments
type argMeta struct {
v reflect.Value
i interface{}
length int
}
var flatArgsCount int
var anySlices bool
var stackMeta [32]argMeta
var meta []argMeta
if len(args) <= len(stackMeta) {
meta = stackMeta[:len(args)]
} else {
meta = make([]argMeta, len(args))
}
for i, arg := range args {
if a, ok := arg.(driver.Valuer); ok {
var err error
arg, err = a.Value()
if err != nil {
return "", nil, err
}
}
if v, ok := asSliceForIn(arg); ok {
meta[i].length = v.Len()
meta[i].v = v
anySlices = true
flatArgsCount += meta[i].length
if meta[i].length == 0 {
return "", nil, errors.New("empty slice passed to 'in' query")
}
} else {
meta[i].i = arg
flatArgsCount++
}
}
// don't do any parsing if there aren't any slices; note that this means
// some errors that we might have caught below will not be returned.
if !anySlices {
return query, args, nil
}
newArgs := make([]interface{}, 0, flatArgsCount)
var buf strings.Builder
buf.Grow(len(query) + len(", ?")*flatArgsCount)
var arg, offset int
for i := strings.IndexByte(query[offset:], '?'); i != -1; i = strings.IndexByte(query[offset:], '?') {
if arg >= len(meta) {
// if an argument wasn't passed, lets return an error; this is
// not actually how database/sql Exec/Query works, but since we are
// creating an argument list programmatically, we want to be able
// to catch these programmer errors earlier.
return "", nil, errors.New("number of bindVars exceeds arguments")
}
argMeta := meta[arg]
arg++
// not a slice, continue.
// our questionmark will either be written before the next expansion
// of a slice or after the loop when writing the rest of the query
if argMeta.length == 0 {
offset = offset + i + 1
newArgs = append(newArgs, argMeta.i)
continue
}
// write everything up to and including our ? character
buf.WriteString(query[:offset+i+1])
for si := 1; si < argMeta.length; si++ {
buf.WriteString(", ?")
}
newArgs = appendReflectSlice(newArgs, argMeta.v, argMeta.length)
// slice the query and reset the offset. this avoids some bookkeeping for
// the write after the loop
query = query[offset+i+1:]
offset = 0
}
buf.WriteString(query)
if arg < len(meta) {
return "", nil, errors.New("number of bindVars less than number arguments")
}
return buf.String(), newArgs, nil
}
func appendReflectSlice(args []interface{}, v reflect.Value, vlen int) []interface{} {
switch val := v.Interface().(type) {
case []interface{}:
args = append(args, val...)
case []int:
for i := range val {
args = append(args, val[i])
}
case []string:
for i := range val {
args = append(args, val[i])
}
default:
for si := 0; si < vlen; si++ {
args = append(args, v.Index(si).Interface())
}
}
return args
}

11
vendor/github.com/jmoiron/sqlx/doc.go generated vendored Normal file
View File

@@ -0,0 +1,11 @@
// Package sqlx provides general purpose extensions to database/sql.
//
// It is intended to seamlessly wrap database/sql and provide convenience
// methods which are useful in the development of database driven applications.
// None of the underlying database/sql methods are changed. Instead all extended
// behavior is implemented through new methods defined on wrapper types.
//
// Additions include scanning into structs, named query support, rebinding
// queries for different drivers, convenient shorthands for common error handling
// and more.
package sqlx

458
vendor/github.com/jmoiron/sqlx/named.go generated vendored Normal file
View File

@@ -0,0 +1,458 @@
package sqlx
// Named Query Support
//
// * BindMap - bind query bindvars to map/struct args
// * NamedExec, NamedQuery - named query w/ struct or map
// * NamedStmt - a pre-compiled named query which is a prepared statement
//
// Internal Interfaces:
//
// * compileNamedQuery - rebind a named query, returning a query and list of names
// * bindArgs, bindMapArgs, bindAnyArgs - given a list of names, return an arglist
//
import (
"bytes"
"database/sql"
"errors"
"fmt"
"reflect"
"regexp"
"strconv"
"unicode"
"github.com/jmoiron/sqlx/reflectx"
)
// NamedStmt is a prepared statement that executes named queries. Prepare it
// how you would execute a NamedQuery, but pass in a struct or map when executing.
type NamedStmt struct {
Params []string
QueryString string
Stmt *Stmt
}
// Close closes the named statement.
func (n *NamedStmt) Close() error {
return n.Stmt.Close()
}
// Exec executes a named statement using the struct passed.
// Any named placeholder parameters are replaced with fields from arg.
func (n *NamedStmt) Exec(arg interface{}) (sql.Result, error) {
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
if err != nil {
return *new(sql.Result), err
}
return n.Stmt.Exec(args...)
}
// Query executes a named statement using the struct argument, returning rows.
// Any named placeholder parameters are replaced with fields from arg.
func (n *NamedStmt) Query(arg interface{}) (*sql.Rows, error) {
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
if err != nil {
return nil, err
}
return n.Stmt.Query(args...)
}
// QueryRow executes a named statement against the database. Because sqlx cannot
// create a *sql.Row with an error condition pre-set for binding errors, sqlx
// returns a *sqlx.Row instead.
// Any named placeholder parameters are replaced with fields from arg.
func (n *NamedStmt) QueryRow(arg interface{}) *Row {
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
if err != nil {
return &Row{err: err}
}
return n.Stmt.QueryRowx(args...)
}
// MustExec execs a NamedStmt, panicing on error
// Any named placeholder parameters are replaced with fields from arg.
func (n *NamedStmt) MustExec(arg interface{}) sql.Result {
res, err := n.Exec(arg)
if err != nil {
panic(err)
}
return res
}
// Queryx using this NamedStmt
// Any named placeholder parameters are replaced with fields from arg.
func (n *NamedStmt) Queryx(arg interface{}) (*Rows, error) {
r, err := n.Query(arg)
if err != nil {
return nil, err
}
return &Rows{Rows: r, Mapper: n.Stmt.Mapper, unsafe: isUnsafe(n)}, err
}
// QueryRowx this NamedStmt. Because of limitations with QueryRow, this is
// an alias for QueryRow.
// Any named placeholder parameters are replaced with fields from arg.
func (n *NamedStmt) QueryRowx(arg interface{}) *Row {
return n.QueryRow(arg)
}
// Select using this NamedStmt
// Any named placeholder parameters are replaced with fields from arg.
func (n *NamedStmt) Select(dest interface{}, arg interface{}) error {
rows, err := n.Queryx(arg)
if err != nil {
return err
}
// if something happens here, we want to make sure the rows are Closed
defer rows.Close()
return scanAll(rows, dest, false)
}
// Get using this NamedStmt
// Any named placeholder parameters are replaced with fields from arg.
func (n *NamedStmt) Get(dest interface{}, arg interface{}) error {
r := n.QueryRowx(arg)
return r.scanAny(dest, false)
}
// Unsafe creates an unsafe version of the NamedStmt
func (n *NamedStmt) Unsafe() *NamedStmt {
r := &NamedStmt{Params: n.Params, Stmt: n.Stmt, QueryString: n.QueryString}
r.Stmt.unsafe = true
return r
}
// A union interface of preparer and binder, required to be able to prepare
// named statements (as the bindtype must be determined).
type namedPreparer interface {
Preparer
binder
}
func prepareNamed(p namedPreparer, query string) (*NamedStmt, error) {
bindType := BindType(p.DriverName())
q, args, err := compileNamedQuery([]byte(query), bindType)
if err != nil {
return nil, err
}
stmt, err := Preparex(p, q)
if err != nil {
return nil, err
}
return &NamedStmt{
QueryString: q,
Params: args,
Stmt: stmt,
}, nil
}
// convertMapStringInterface attempts to convert v to map[string]interface{}.
// Unlike v.(map[string]interface{}), this function works on named types that
// are convertible to map[string]interface{} as well.
func convertMapStringInterface(v interface{}) (map[string]interface{}, bool) {
var m map[string]interface{}
mtype := reflect.TypeOf(m)
t := reflect.TypeOf(v)
if !t.ConvertibleTo(mtype) {
return nil, false
}
return reflect.ValueOf(v).Convert(mtype).Interface().(map[string]interface{}), true
}
func bindAnyArgs(names []string, arg interface{}, m *reflectx.Mapper) ([]interface{}, error) {
if maparg, ok := convertMapStringInterface(arg); ok {
return bindMapArgs(names, maparg)
}
return bindArgs(names, arg, m)
}
// private interface to generate a list of interfaces from a given struct
// type, given a list of names to pull out of the struct. Used by public
// BindStruct interface.
func bindArgs(names []string, arg interface{}, m *reflectx.Mapper) ([]interface{}, error) {
arglist := make([]interface{}, 0, len(names))
// grab the indirected value of arg
var v reflect.Value
for v = reflect.ValueOf(arg); v.Kind() == reflect.Ptr; {
v = v.Elem()
}
err := m.TraversalsByNameFunc(v.Type(), names, func(i int, t []int) error {
if len(t) == 0 {
return fmt.Errorf("could not find name %s in %#v", names[i], arg)
}
val := reflectx.FieldByIndexesReadOnly(v, t)
arglist = append(arglist, val.Interface())
return nil
})
return arglist, err
}
// like bindArgs, but for maps.
func bindMapArgs(names []string, arg map[string]interface{}) ([]interface{}, error) {
arglist := make([]interface{}, 0, len(names))
for _, name := range names {
val, ok := arg[name]
if !ok {
return arglist, fmt.Errorf("could not find name %s in %#v", name, arg)
}
arglist = append(arglist, val)
}
return arglist, nil
}
// bindStruct binds a named parameter query with fields from a struct argument.
// The rules for binding field names to parameter names follow the same
// conventions as for StructScan, including obeying the `db` struct tags.
func bindStruct(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) {
bound, names, err := compileNamedQuery([]byte(query), bindType)
if err != nil {
return "", []interface{}{}, err
}
arglist, err := bindAnyArgs(names, arg, m)
if err != nil {
return "", []interface{}{}, err
}
return bound, arglist, nil
}
var valuesReg = regexp.MustCompile(`\)\s*(?i)VALUES\s*\(`)
func findMatchingClosingBracketIndex(s string) int {
count := 0
for i, ch := range s {
if ch == '(' {
count++
}
if ch == ')' {
count--
if count == 0 {
return i
}
}
}
return 0
}
func fixBound(bound string, loop int) string {
loc := valuesReg.FindStringIndex(bound)
// defensive guard when "VALUES (...)" not found
if len(loc) < 2 {
return bound
}
openingBracketIndex := loc[1] - 1
index := findMatchingClosingBracketIndex(bound[openingBracketIndex:])
// defensive guard. must have closing bracket
if index == 0 {
return bound
}
closingBracketIndex := openingBracketIndex + index + 1
var buffer bytes.Buffer
buffer.WriteString(bound[0:closingBracketIndex])
for i := 0; i < loop-1; i++ {
buffer.WriteString(",")
buffer.WriteString(bound[openingBracketIndex:closingBracketIndex])
}
buffer.WriteString(bound[closingBracketIndex:])
return buffer.String()
}
// bindArray binds a named parameter query with fields from an array or slice of
// structs argument.
func bindArray(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) {
// do the initial binding with QUESTION; if bindType is not question,
// we can rebind it at the end.
bound, names, err := compileNamedQuery([]byte(query), QUESTION)
if err != nil {
return "", []interface{}{}, err
}
arrayValue := reflect.ValueOf(arg)
arrayLen := arrayValue.Len()
if arrayLen == 0 {
return "", []interface{}{}, fmt.Errorf("length of array is 0: %#v", arg)
}
var arglist = make([]interface{}, 0, len(names)*arrayLen)
for i := 0; i < arrayLen; i++ {
elemArglist, err := bindAnyArgs(names, arrayValue.Index(i).Interface(), m)
if err != nil {
return "", []interface{}{}, err
}
arglist = append(arglist, elemArglist...)
}
if arrayLen > 1 {
bound = fixBound(bound, arrayLen)
}
// adjust binding type if we weren't on question
if bindType != QUESTION {
bound = Rebind(bindType, bound)
}
return bound, arglist, nil
}
// bindMap binds a named parameter query with a map of arguments.
func bindMap(bindType int, query string, args map[string]interface{}) (string, []interface{}, error) {
bound, names, err := compileNamedQuery([]byte(query), bindType)
if err != nil {
return "", []interface{}{}, err
}
arglist, err := bindMapArgs(names, args)
return bound, arglist, err
}
// -- Compilation of Named Queries
// Allow digits and letters in bind params; additionally runes are
// checked against underscores, meaning that bind params can have be
// alphanumeric with underscores. Mind the difference between unicode
// digits and numbers, where '5' is a digit but '五' is not.
var allowedBindRunes = []*unicode.RangeTable{unicode.Letter, unicode.Digit}
// FIXME: this function isn't safe for unicode named params, as a failing test
// can testify. This is not a regression but a failure of the original code
// as well. It should be modified to range over runes in a string rather than
// bytes, even though this is less convenient and slower. Hopefully the
// addition of the prepared NamedStmt (which will only do this once) will make
// up for the slightly slower ad-hoc NamedExec/NamedQuery.
// compile a NamedQuery into an unbound query (using the '?' bindvar) and
// a list of names.
func compileNamedQuery(qs []byte, bindType int) (query string, names []string, err error) {
names = make([]string, 0, 10)
rebound := make([]byte, 0, len(qs))
inName := false
last := len(qs) - 1
currentVar := 1
name := make([]byte, 0, 10)
for i, b := range qs {
// a ':' while we're in a name is an error
if b == ':' {
// if this is the second ':' in a '::' escape sequence, append a ':'
if inName && i > 0 && qs[i-1] == ':' {
rebound = append(rebound, ':')
inName = false
continue
} else if inName {
err = errors.New("unexpected `:` while reading named param at " + strconv.Itoa(i))
return query, names, err
}
inName = true
name = []byte{}
} else if inName && i > 0 && b == '=' && len(name) == 0 {
rebound = append(rebound, ':', '=')
inName = false
continue
// if we're in a name, and this is an allowed character, continue
} else if inName && (unicode.IsOneOf(allowedBindRunes, rune(b)) || b == '_' || b == '.') && i != last {
// append the byte to the name if we are in a name and not on the last byte
name = append(name, b)
// if we're in a name and it's not an allowed character, the name is done
} else if inName {
inName = false
// if this is the final byte of the string and it is part of the name, then
// make sure to add it to the name
if i == last && unicode.IsOneOf(allowedBindRunes, rune(b)) {
name = append(name, b)
}
// add the string representation to the names list
names = append(names, string(name))
// add a proper bindvar for the bindType
switch bindType {
// oracle only supports named type bind vars even for positional
case NAMED:
rebound = append(rebound, ':')
rebound = append(rebound, name...)
case QUESTION, UNKNOWN:
rebound = append(rebound, '?')
case DOLLAR:
rebound = append(rebound, '$')
for _, b := range strconv.Itoa(currentVar) {
rebound = append(rebound, byte(b))
}
currentVar++
case AT:
rebound = append(rebound, '@', 'p')
for _, b := range strconv.Itoa(currentVar) {
rebound = append(rebound, byte(b))
}
currentVar++
}
// add this byte to string unless it was not part of the name
if i != last {
rebound = append(rebound, b)
} else if !unicode.IsOneOf(allowedBindRunes, rune(b)) {
rebound = append(rebound, b)
}
} else {
// this is a normal byte and should just go onto the rebound query
rebound = append(rebound, b)
}
}
return string(rebound), names, err
}
// BindNamed binds a struct or a map to a query with named parameters.
// DEPRECATED: use sqlx.Named` instead of this, it may be removed in future.
func BindNamed(bindType int, query string, arg interface{}) (string, []interface{}, error) {
return bindNamedMapper(bindType, query, arg, mapper())
}
// Named takes a query using named parameters and an argument and
// returns a new query with a list of args that can be executed by
// a database. The return value uses the `?` bindvar.
func Named(query string, arg interface{}) (string, []interface{}, error) {
return bindNamedMapper(QUESTION, query, arg, mapper())
}
func bindNamedMapper(bindType int, query string, arg interface{}, m *reflectx.Mapper) (string, []interface{}, error) {
t := reflect.TypeOf(arg)
k := t.Kind()
switch {
case k == reflect.Map && t.Key().Kind() == reflect.String:
m, ok := convertMapStringInterface(arg)
if !ok {
return "", nil, fmt.Errorf("sqlx.bindNamedMapper: unsupported map type: %T", arg)
}
return bindMap(bindType, query, m)
case k == reflect.Array || k == reflect.Slice:
return bindArray(bindType, query, arg, m)
default:
return bindStruct(bindType, query, arg, m)
}
}
// NamedQuery binds a named query and then runs Query on the result using the
// provided Ext (sqlx.Tx, sqlx.Db). It works with both structs and with
// map[string]interface{} types.
func NamedQuery(e Ext, query string, arg interface{}) (*Rows, error) {
q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e))
if err != nil {
return nil, err
}
return e.Queryx(q, args...)
}
// NamedExec uses BindStruct to get a query executable by the driver and
// then runs Exec on the result. Returns an error from the binding
// or the query execution itself.
func NamedExec(e Ext, query string, arg interface{}) (sql.Result, error) {
q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e))
if err != nil {
return nil, err
}
return e.Exec(q, args...)
}

133
vendor/github.com/jmoiron/sqlx/named_context.go generated vendored Normal file
View File

@@ -0,0 +1,133 @@
//go:build go1.8
// +build go1.8
package sqlx
import (
"context"
"database/sql"
)
// A union interface of contextPreparer and binder, required to be able to
// prepare named statements with context (as the bindtype must be determined).
type namedPreparerContext interface {
PreparerContext
binder
}
func prepareNamedContext(ctx context.Context, p namedPreparerContext, query string) (*NamedStmt, error) {
bindType := BindType(p.DriverName())
q, args, err := compileNamedQuery([]byte(query), bindType)
if err != nil {
return nil, err
}
stmt, err := PreparexContext(ctx, p, q)
if err != nil {
return nil, err
}
return &NamedStmt{
QueryString: q,
Params: args,
Stmt: stmt,
}, nil
}
// ExecContext executes a named statement using the struct passed.
// Any named placeholder parameters are replaced with fields from arg.
func (n *NamedStmt) ExecContext(ctx context.Context, arg interface{}) (sql.Result, error) {
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
if err != nil {
return *new(sql.Result), err
}
return n.Stmt.ExecContext(ctx, args...)
}
// QueryContext executes a named statement using the struct argument, returning rows.
// Any named placeholder parameters are replaced with fields from arg.
func (n *NamedStmt) QueryContext(ctx context.Context, arg interface{}) (*sql.Rows, error) {
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
if err != nil {
return nil, err
}
return n.Stmt.QueryContext(ctx, args...)
}
// QueryRowContext executes a named statement against the database. Because sqlx cannot
// create a *sql.Row with an error condition pre-set for binding errors, sqlx
// returns a *sqlx.Row instead.
// Any named placeholder parameters are replaced with fields from arg.
func (n *NamedStmt) QueryRowContext(ctx context.Context, arg interface{}) *Row {
args, err := bindAnyArgs(n.Params, arg, n.Stmt.Mapper)
if err != nil {
return &Row{err: err}
}
return n.Stmt.QueryRowxContext(ctx, args...)
}
// MustExecContext execs a NamedStmt, panicing on error
// Any named placeholder parameters are replaced with fields from arg.
func (n *NamedStmt) MustExecContext(ctx context.Context, arg interface{}) sql.Result {
res, err := n.ExecContext(ctx, arg)
if err != nil {
panic(err)
}
return res
}
// QueryxContext using this NamedStmt
// Any named placeholder parameters are replaced with fields from arg.
func (n *NamedStmt) QueryxContext(ctx context.Context, arg interface{}) (*Rows, error) {
r, err := n.QueryContext(ctx, arg)
if err != nil {
return nil, err
}
return &Rows{Rows: r, Mapper: n.Stmt.Mapper, unsafe: isUnsafe(n)}, err
}
// QueryRowxContext this NamedStmt. Because of limitations with QueryRow, this is
// an alias for QueryRow.
// Any named placeholder parameters are replaced with fields from arg.
func (n *NamedStmt) QueryRowxContext(ctx context.Context, arg interface{}) *Row {
return n.QueryRowContext(ctx, arg)
}
// SelectContext using this NamedStmt
// Any named placeholder parameters are replaced with fields from arg.
func (n *NamedStmt) SelectContext(ctx context.Context, dest interface{}, arg interface{}) error {
rows, err := n.QueryxContext(ctx, arg)
if err != nil {
return err
}
// if something happens here, we want to make sure the rows are Closed
defer rows.Close()
return scanAll(rows, dest, false)
}
// GetContext using this NamedStmt
// Any named placeholder parameters are replaced with fields from arg.
func (n *NamedStmt) GetContext(ctx context.Context, dest interface{}, arg interface{}) error {
r := n.QueryRowxContext(ctx, arg)
return r.scanAny(dest, false)
}
// NamedQueryContext binds a named query and then runs Query on the result using the
// provided Ext (sqlx.Tx, sqlx.Db). It works with both structs and with
// map[string]interface{} types.
func NamedQueryContext(ctx context.Context, e ExtContext, query string, arg interface{}) (*Rows, error) {
q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e))
if err != nil {
return nil, err
}
return e.QueryxContext(ctx, q, args...)
}
// NamedExecContext uses BindStruct to get a query executable by the driver and
// then runs Exec on the result. Returns an error from the binding
// or the query execution itself.
func NamedExecContext(ctx context.Context, e ExtContext, query string, arg interface{}) (sql.Result, error) {
q, args, err := bindNamedMapper(BindType(e.DriverName()), query, arg, mapperFor(e))
if err != nil {
return nil, err
}
return e.ExecContext(ctx, q, args...)
}

17
vendor/github.com/jmoiron/sqlx/reflectx/README.md generated vendored Normal file
View File

@@ -0,0 +1,17 @@
# reflectx
The sqlx package has special reflect needs. In particular, it needs to:
* be able to map a name to a field
* understand embedded structs
* understand mapping names to fields by a particular tag
* user specified name -> field mapping functions
These behaviors mimic the behaviors by the standard library marshallers and also the
behavior of standard Go accessors.
The first two are amply taken care of by `Reflect.Value.FieldByName`, and the third is
addressed by `Reflect.Value.FieldByNameFunc`, but these don't quite understand struct
tags in the ways that are vital to most marshallers, and they are slow.
This reflectx package extends reflect to achieve these goals.

443
vendor/github.com/jmoiron/sqlx/reflectx/reflect.go generated vendored Normal file
View File

@@ -0,0 +1,443 @@
// Package reflectx implements extensions to the standard reflect lib suitable
// for implementing marshalling and unmarshalling packages. The main Mapper type
// allows for Go-compatible named attribute access, including accessing embedded
// struct attributes and the ability to use functions and struct tags to
// customize field names.
package reflectx
import (
"reflect"
"runtime"
"strings"
"sync"
)
// A FieldInfo is metadata for a struct field.
type FieldInfo struct {
Index []int
Path string
Field reflect.StructField
Zero reflect.Value
Name string
Options map[string]string
Embedded bool
Children []*FieldInfo
Parent *FieldInfo
}
// A StructMap is an index of field metadata for a struct.
type StructMap struct {
Tree *FieldInfo
Index []*FieldInfo
Paths map[string]*FieldInfo
Names map[string]*FieldInfo
}
// GetByPath returns a *FieldInfo for a given string path.
func (f StructMap) GetByPath(path string) *FieldInfo {
return f.Paths[path]
}
// GetByTraversal returns a *FieldInfo for a given integer path. It is
// analogous to reflect.FieldByIndex, but using the cached traversal
// rather than re-executing the reflect machinery each time.
func (f StructMap) GetByTraversal(index []int) *FieldInfo {
if len(index) == 0 {
return nil
}
tree := f.Tree
for _, i := range index {
if i >= len(tree.Children) || tree.Children[i] == nil {
return nil
}
tree = tree.Children[i]
}
return tree
}
// Mapper is a general purpose mapper of names to struct fields. A Mapper
// behaves like most marshallers in the standard library, obeying a field tag
// for name mapping but also providing a basic transform function.
type Mapper struct {
cache map[reflect.Type]*StructMap
tagName string
tagMapFunc func(string) string
mapFunc func(string) string
mutex sync.Mutex
}
// NewMapper returns a new mapper using the tagName as its struct field tag.
// If tagName is the empty string, it is ignored.
func NewMapper(tagName string) *Mapper {
return &Mapper{
cache: make(map[reflect.Type]*StructMap),
tagName: tagName,
}
}
// NewMapperTagFunc returns a new mapper which contains a mapper for field names
// AND a mapper for tag values. This is useful for tags like json which can
// have values like "name,omitempty".
func NewMapperTagFunc(tagName string, mapFunc, tagMapFunc func(string) string) *Mapper {
return &Mapper{
cache: make(map[reflect.Type]*StructMap),
tagName: tagName,
mapFunc: mapFunc,
tagMapFunc: tagMapFunc,
}
}
// NewMapperFunc returns a new mapper which optionally obeys a field tag and
// a struct field name mapper func given by f. Tags will take precedence, but
// for any other field, the mapped name will be f(field.Name)
func NewMapperFunc(tagName string, f func(string) string) *Mapper {
return &Mapper{
cache: make(map[reflect.Type]*StructMap),
tagName: tagName,
mapFunc: f,
}
}
// TypeMap returns a mapping of field strings to int slices representing
// the traversal down the struct to reach the field.
func (m *Mapper) TypeMap(t reflect.Type) *StructMap {
m.mutex.Lock()
mapping, ok := m.cache[t]
if !ok {
mapping = getMapping(t, m.tagName, m.mapFunc, m.tagMapFunc)
m.cache[t] = mapping
}
m.mutex.Unlock()
return mapping
}
// FieldMap returns the mapper's mapping of field names to reflect values. Panics
// if v's Kind is not Struct, or v is not Indirectable to a struct kind.
func (m *Mapper) FieldMap(v reflect.Value) map[string]reflect.Value {
v = reflect.Indirect(v)
mustBe(v, reflect.Struct)
r := map[string]reflect.Value{}
tm := m.TypeMap(v.Type())
for tagName, fi := range tm.Names {
r[tagName] = FieldByIndexes(v, fi.Index)
}
return r
}
// FieldByName returns a field by its mapped name as a reflect.Value.
// Panics if v's Kind is not Struct or v is not Indirectable to a struct Kind.
// Returns zero Value if the name is not found.
func (m *Mapper) FieldByName(v reflect.Value, name string) reflect.Value {
v = reflect.Indirect(v)
mustBe(v, reflect.Struct)
tm := m.TypeMap(v.Type())
fi, ok := tm.Names[name]
if !ok {
return v
}
return FieldByIndexes(v, fi.Index)
}
// FieldsByName returns a slice of values corresponding to the slice of names
// for the value. Panics if v's Kind is not Struct or v is not Indirectable
// to a struct Kind. Returns zero Value for each name not found.
func (m *Mapper) FieldsByName(v reflect.Value, names []string) []reflect.Value {
v = reflect.Indirect(v)
mustBe(v, reflect.Struct)
tm := m.TypeMap(v.Type())
vals := make([]reflect.Value, 0, len(names))
for _, name := range names {
fi, ok := tm.Names[name]
if !ok {
vals = append(vals, *new(reflect.Value))
} else {
vals = append(vals, FieldByIndexes(v, fi.Index))
}
}
return vals
}
// TraversalsByName returns a slice of int slices which represent the struct
// traversals for each mapped name. Panics if t is not a struct or Indirectable
// to a struct. Returns empty int slice for each name not found.
func (m *Mapper) TraversalsByName(t reflect.Type, names []string) [][]int {
r := make([][]int, 0, len(names))
m.TraversalsByNameFunc(t, names, func(_ int, i []int) error {
if i == nil {
r = append(r, []int{})
} else {
r = append(r, i)
}
return nil
})
return r
}
// TraversalsByNameFunc traverses the mapped names and calls fn with the index of
// each name and the struct traversal represented by that name. Panics if t is not
// a struct or Indirectable to a struct. Returns the first error returned by fn or nil.
func (m *Mapper) TraversalsByNameFunc(t reflect.Type, names []string, fn func(int, []int) error) error {
t = Deref(t)
mustBe(t, reflect.Struct)
tm := m.TypeMap(t)
for i, name := range names {
fi, ok := tm.Names[name]
if !ok {
if err := fn(i, nil); err != nil {
return err
}
} else {
if err := fn(i, fi.Index); err != nil {
return err
}
}
}
return nil
}
// FieldByIndexes returns a value for the field given by the struct traversal
// for the given value.
func FieldByIndexes(v reflect.Value, indexes []int) reflect.Value {
for _, i := range indexes {
v = reflect.Indirect(v).Field(i)
// if this is a pointer and it's nil, allocate a new value and set it
if v.Kind() == reflect.Ptr && v.IsNil() {
alloc := reflect.New(Deref(v.Type()))
v.Set(alloc)
}
if v.Kind() == reflect.Map && v.IsNil() {
v.Set(reflect.MakeMap(v.Type()))
}
}
return v
}
// FieldByIndexesReadOnly returns a value for a particular struct traversal,
// but is not concerned with allocating nil pointers because the value is
// going to be used for reading and not setting.
func FieldByIndexesReadOnly(v reflect.Value, indexes []int) reflect.Value {
for _, i := range indexes {
v = reflect.Indirect(v).Field(i)
}
return v
}
// Deref is Indirect for reflect.Types
func Deref(t reflect.Type) reflect.Type {
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
return t
}
// -- helpers & utilities --
type kinder interface {
Kind() reflect.Kind
}
// mustBe checks a value against a kind, panicing with a reflect.ValueError
// if the kind isn't that which is required.
func mustBe(v kinder, expected reflect.Kind) {
if k := v.Kind(); k != expected {
panic(&reflect.ValueError{Method: methodName(), Kind: k})
}
}
// methodName returns the caller of the function calling methodName
func methodName() string {
pc, _, _, _ := runtime.Caller(2)
f := runtime.FuncForPC(pc)
if f == nil {
return "unknown method"
}
return f.Name()
}
type typeQueue struct {
t reflect.Type
fi *FieldInfo
pp string // Parent path
}
// A copying append that creates a new slice each time.
func apnd(is []int, i int) []int {
x := make([]int, len(is)+1)
copy(x, is)
x[len(x)-1] = i
return x
}
type mapf func(string) string
// parseName parses the tag and the target name for the given field using
// the tagName (eg 'json' for `json:"foo"` tags), mapFunc for mapping the
// field's name to a target name, and tagMapFunc for mapping the tag to
// a target name.
func parseName(field reflect.StructField, tagName string, mapFunc, tagMapFunc mapf) (tag, fieldName string) {
// first, set the fieldName to the field's name
fieldName = field.Name
// if a mapFunc is set, use that to override the fieldName
if mapFunc != nil {
fieldName = mapFunc(fieldName)
}
// if there's no tag to look for, return the field name
if tagName == "" {
return "", fieldName
}
// if this tag is not set using the normal convention in the tag,
// then return the fieldname.. this check is done because according
// to the reflect documentation:
// If the tag does not have the conventional format,
// the value returned by Get is unspecified.
// which doesn't sound great.
if !strings.Contains(string(field.Tag), tagName+":") {
return "", fieldName
}
// at this point we're fairly sure that we have a tag, so lets pull it out
tag = field.Tag.Get(tagName)
// if we have a mapper function, call it on the whole tag
// XXX: this is a change from the old version, which pulled out the name
// before the tagMapFunc could be run, but I think this is the right way
if tagMapFunc != nil {
tag = tagMapFunc(tag)
}
// finally, split the options from the name
parts := strings.Split(tag, ",")
fieldName = parts[0]
return tag, fieldName
}
// parseOptions parses options out of a tag string, skipping the name
func parseOptions(tag string) map[string]string {
parts := strings.Split(tag, ",")
options := make(map[string]string, len(parts))
if len(parts) > 1 {
for _, opt := range parts[1:] {
// short circuit potentially expensive split op
if strings.Contains(opt, "=") {
kv := strings.Split(opt, "=")
options[kv[0]] = kv[1]
continue
}
options[opt] = ""
}
}
return options
}
// getMapping returns a mapping for the t type, using the tagName, mapFunc and
// tagMapFunc to determine the canonical names of fields.
func getMapping(t reflect.Type, tagName string, mapFunc, tagMapFunc mapf) *StructMap {
m := []*FieldInfo{}
root := &FieldInfo{}
queue := []typeQueue{}
queue = append(queue, typeQueue{Deref(t), root, ""})
QueueLoop:
for len(queue) != 0 {
// pop the first item off of the queue
tq := queue[0]
queue = queue[1:]
// ignore recursive field
for p := tq.fi.Parent; p != nil; p = p.Parent {
if tq.fi.Field.Type == p.Field.Type {
continue QueueLoop
}
}
nChildren := 0
if tq.t.Kind() == reflect.Struct {
nChildren = tq.t.NumField()
}
tq.fi.Children = make([]*FieldInfo, nChildren)
// iterate through all of its fields
for fieldPos := 0; fieldPos < nChildren; fieldPos++ {
f := tq.t.Field(fieldPos)
// parse the tag and the target name using the mapping options for this field
tag, name := parseName(f, tagName, mapFunc, tagMapFunc)
// if the name is "-", disabled via a tag, skip it
if name == "-" {
continue
}
fi := FieldInfo{
Field: f,
Name: name,
Zero: reflect.New(f.Type).Elem(),
Options: parseOptions(tag),
}
// if the path is empty this path is just the name
if tq.pp == "" {
fi.Path = fi.Name
} else {
fi.Path = tq.pp + "." + fi.Name
}
// skip unexported fields
if len(f.PkgPath) != 0 && !f.Anonymous {
continue
}
// bfs search of anonymous embedded structs
if f.Anonymous {
pp := tq.pp
if tag != "" {
pp = fi.Path
}
fi.Embedded = true
fi.Index = apnd(tq.fi.Index, fieldPos)
nChildren := 0
ft := Deref(f.Type)
if ft.Kind() == reflect.Struct {
nChildren = ft.NumField()
}
fi.Children = make([]*FieldInfo, nChildren)
queue = append(queue, typeQueue{Deref(f.Type), &fi, pp})
} else if fi.Zero.Kind() == reflect.Struct || (fi.Zero.Kind() == reflect.Ptr && fi.Zero.Type().Elem().Kind() == reflect.Struct) {
fi.Index = apnd(tq.fi.Index, fieldPos)
fi.Children = make([]*FieldInfo, Deref(f.Type).NumField())
queue = append(queue, typeQueue{Deref(f.Type), &fi, fi.Path})
}
fi.Index = apnd(tq.fi.Index, fieldPos)
fi.Parent = tq.fi
tq.fi.Children[fieldPos] = &fi
m = append(m, &fi)
}
}
flds := &StructMap{Index: m, Tree: root, Paths: map[string]*FieldInfo{}, Names: map[string]*FieldInfo{}}
for _, fi := range flds.Index {
// check if nothing has already been pushed with the same path
// sometimes you can choose to override a type using embedded struct
fld, ok := flds.Paths[fi.Path]
if !ok || fld.Embedded {
flds.Paths[fi.Path] = fi
if fi.Name != "" && !fi.Embedded {
flds.Names[fi.Path] = fi
}
}
}
return flds
}

1054
vendor/github.com/jmoiron/sqlx/sqlx.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

415
vendor/github.com/jmoiron/sqlx/sqlx_context.go generated vendored Normal file
View File

@@ -0,0 +1,415 @@
//go:build go1.8
// +build go1.8
package sqlx
import (
"context"
"database/sql"
"fmt"
"io/ioutil"
"path/filepath"
"reflect"
)
// ConnectContext to a database and verify with a ping.
func ConnectContext(ctx context.Context, driverName, dataSourceName string) (*DB, error) {
db, err := Open(driverName, dataSourceName)
if err != nil {
return db, err
}
err = db.PingContext(ctx)
return db, err
}
// QueryerContext is an interface used by GetContext and SelectContext
type QueryerContext interface {
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error)
QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row
}
// PreparerContext is an interface used by PreparexContext.
type PreparerContext interface {
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
}
// ExecerContext is an interface used by MustExecContext and LoadFileContext
type ExecerContext interface {
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
}
// ExtContext is a union interface which can bind, query, and exec, with Context
// used by NamedQueryContext and NamedExecContext.
type ExtContext interface {
binder
QueryerContext
ExecerContext
}
// SelectContext executes a query using the provided Queryer, and StructScans
// each row into dest, which must be a slice. If the slice elements are
// scannable, then the result set must have only one column. Otherwise,
// StructScan is used. The *sql.Rows are closed automatically.
// Any placeholder parameters are replaced with supplied args.
func SelectContext(ctx context.Context, q QueryerContext, dest interface{}, query string, args ...interface{}) error {
rows, err := q.QueryxContext(ctx, query, args...)
if err != nil {
return err
}
// if something happens here, we want to make sure the rows are Closed
defer rows.Close()
return scanAll(rows, dest, false)
}
// PreparexContext prepares a statement.
//
// The provided context is used for the preparation of the statement, not for
// the execution of the statement.
func PreparexContext(ctx context.Context, p PreparerContext, query string) (*Stmt, error) {
s, err := p.PrepareContext(ctx, query)
if err != nil {
return nil, err
}
return &Stmt{Stmt: s, unsafe: isUnsafe(p), Mapper: mapperFor(p)}, err
}
// GetContext does a QueryRow using the provided Queryer, and scans the
// resulting row to dest. If dest is scannable, the result must only have one
// column. Otherwise, StructScan is used. Get will return sql.ErrNoRows like
// row.Scan would. Any placeholder parameters are replaced with supplied args.
// An error is returned if the result set is empty.
func GetContext(ctx context.Context, q QueryerContext, dest interface{}, query string, args ...interface{}) error {
r := q.QueryRowxContext(ctx, query, args...)
return r.scanAny(dest, false)
}
// LoadFileContext exec's every statement in a file (as a single call to Exec).
// LoadFileContext may return a nil *sql.Result if errors are encountered
// locating or reading the file at path. LoadFile reads the entire file into
// memory, so it is not suitable for loading large data dumps, but can be useful
// for initializing schemas or loading indexes.
//
// FIXME: this does not really work with multi-statement files for mattn/go-sqlite3
// or the go-mysql-driver/mysql drivers; pq seems to be an exception here. Detecting
// this by requiring something with DriverName() and then attempting to split the
// queries will be difficult to get right, and its current driver-specific behavior
// is deemed at least not complex in its incorrectness.
func LoadFileContext(ctx context.Context, e ExecerContext, path string) (*sql.Result, error) {
realpath, err := filepath.Abs(path)
if err != nil {
return nil, err
}
contents, err := ioutil.ReadFile(realpath)
if err != nil {
return nil, err
}
res, err := e.ExecContext(ctx, string(contents))
return &res, err
}
// MustExecContext execs the query using e and panics if there was an error.
// Any placeholder parameters are replaced with supplied args.
func MustExecContext(ctx context.Context, e ExecerContext, query string, args ...interface{}) sql.Result {
res, err := e.ExecContext(ctx, query, args...)
if err != nil {
panic(err)
}
return res
}
// PrepareNamedContext returns an sqlx.NamedStmt
func (db *DB) PrepareNamedContext(ctx context.Context, query string) (*NamedStmt, error) {
return prepareNamedContext(ctx, db, query)
}
// NamedQueryContext using this DB.
// Any named placeholder parameters are replaced with fields from arg.
func (db *DB) NamedQueryContext(ctx context.Context, query string, arg interface{}) (*Rows, error) {
return NamedQueryContext(ctx, db, query, arg)
}
// NamedExecContext using this DB.
// Any named placeholder parameters are replaced with fields from arg.
func (db *DB) NamedExecContext(ctx context.Context, query string, arg interface{}) (sql.Result, error) {
return NamedExecContext(ctx, db, query, arg)
}
// SelectContext using this DB.
// Any placeholder parameters are replaced with supplied args.
func (db *DB) SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
return SelectContext(ctx, db, dest, query, args...)
}
// GetContext using this DB.
// Any placeholder parameters are replaced with supplied args.
// An error is returned if the result set is empty.
func (db *DB) GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
return GetContext(ctx, db, dest, query, args...)
}
// PreparexContext returns an sqlx.Stmt instead of a sql.Stmt.
//
// The provided context is used for the preparation of the statement, not for
// the execution of the statement.
func (db *DB) PreparexContext(ctx context.Context, query string) (*Stmt, error) {
return PreparexContext(ctx, db, query)
}
// QueryxContext queries the database and returns an *sqlx.Rows.
// Any placeholder parameters are replaced with supplied args.
func (db *DB) QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) {
r, err := db.DB.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
return &Rows{Rows: r, unsafe: db.unsafe, Mapper: db.Mapper}, err
}
// QueryRowxContext queries the database and returns an *sqlx.Row.
// Any placeholder parameters are replaced with supplied args.
func (db *DB) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row {
rows, err := db.DB.QueryContext(ctx, query, args...)
return &Row{rows: rows, err: err, unsafe: db.unsafe, Mapper: db.Mapper}
}
// MustBeginTx starts a transaction, and panics on error. Returns an *sqlx.Tx instead
// of an *sql.Tx.
//
// The provided context is used until the transaction is committed or rolled
// back. If the context is canceled, the sql package will roll back the
// transaction. Tx.Commit will return an error if the context provided to
// MustBeginContext is canceled.
func (db *DB) MustBeginTx(ctx context.Context, opts *sql.TxOptions) *Tx {
tx, err := db.BeginTxx(ctx, opts)
if err != nil {
panic(err)
}
return tx
}
// MustExecContext (panic) runs MustExec using this database.
// Any placeholder parameters are replaced with supplied args.
func (db *DB) MustExecContext(ctx context.Context, query string, args ...interface{}) sql.Result {
return MustExecContext(ctx, db, query, args...)
}
// BeginTxx begins a transaction and returns an *sqlx.Tx instead of an
// *sql.Tx.
//
// The provided context is used until the transaction is committed or rolled
// back. If the context is canceled, the sql package will roll back the
// transaction. Tx.Commit will return an error if the context provided to
// BeginxContext is canceled.
func (db *DB) BeginTxx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) {
tx, err := db.DB.BeginTx(ctx, opts)
if err != nil {
return nil, err
}
return &Tx{Tx: tx, driverName: db.driverName, unsafe: db.unsafe, Mapper: db.Mapper}, err
}
// Connx returns an *sqlx.Conn instead of an *sql.Conn.
func (db *DB) Connx(ctx context.Context) (*Conn, error) {
conn, err := db.DB.Conn(ctx)
if err != nil {
return nil, err
}
return &Conn{Conn: conn, driverName: db.driverName, unsafe: db.unsafe, Mapper: db.Mapper}, nil
}
// BeginTxx begins a transaction and returns an *sqlx.Tx instead of an
// *sql.Tx.
//
// The provided context is used until the transaction is committed or rolled
// back. If the context is canceled, the sql package will roll back the
// transaction. Tx.Commit will return an error if the context provided to
// BeginxContext is canceled.
func (c *Conn) BeginTxx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) {
tx, err := c.Conn.BeginTx(ctx, opts)
if err != nil {
return nil, err
}
return &Tx{Tx: tx, driverName: c.driverName, unsafe: c.unsafe, Mapper: c.Mapper}, err
}
// SelectContext using this Conn.
// Any placeholder parameters are replaced with supplied args.
func (c *Conn) SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
return SelectContext(ctx, c, dest, query, args...)
}
// GetContext using this Conn.
// Any placeholder parameters are replaced with supplied args.
// An error is returned if the result set is empty.
func (c *Conn) GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
return GetContext(ctx, c, dest, query, args...)
}
// PreparexContext returns an sqlx.Stmt instead of a sql.Stmt.
//
// The provided context is used for the preparation of the statement, not for
// the execution of the statement.
func (c *Conn) PreparexContext(ctx context.Context, query string) (*Stmt, error) {
return PreparexContext(ctx, c, query)
}
// QueryxContext queries the database and returns an *sqlx.Rows.
// Any placeholder parameters are replaced with supplied args.
func (c *Conn) QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) {
r, err := c.Conn.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
return &Rows{Rows: r, unsafe: c.unsafe, Mapper: c.Mapper}, err
}
// QueryRowxContext queries the database and returns an *sqlx.Row.
// Any placeholder parameters are replaced with supplied args.
func (c *Conn) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row {
rows, err := c.Conn.QueryContext(ctx, query, args...)
return &Row{rows: rows, err: err, unsafe: c.unsafe, Mapper: c.Mapper}
}
// Rebind a query within a Conn's bindvar type.
func (c *Conn) Rebind(query string) string {
return Rebind(BindType(c.driverName), query)
}
// StmtxContext returns a version of the prepared statement which runs within a
// transaction. Provided stmt can be either *sql.Stmt or *sqlx.Stmt.
func (tx *Tx) StmtxContext(ctx context.Context, stmt interface{}) *Stmt {
var s *sql.Stmt
switch v := stmt.(type) {
case Stmt:
s = v.Stmt
case *Stmt:
s = v.Stmt
case *sql.Stmt:
s = v
default:
panic(fmt.Sprintf("non-statement type %v passed to Stmtx", reflect.ValueOf(stmt).Type()))
}
return &Stmt{Stmt: tx.StmtContext(ctx, s), Mapper: tx.Mapper}
}
// NamedStmtContext returns a version of the prepared statement which runs
// within a transaction.
func (tx *Tx) NamedStmtContext(ctx context.Context, stmt *NamedStmt) *NamedStmt {
return &NamedStmt{
QueryString: stmt.QueryString,
Params: stmt.Params,
Stmt: tx.StmtxContext(ctx, stmt.Stmt),
}
}
// PreparexContext returns an sqlx.Stmt instead of a sql.Stmt.
//
// The provided context is used for the preparation of the statement, not for
// the execution of the statement.
func (tx *Tx) PreparexContext(ctx context.Context, query string) (*Stmt, error) {
return PreparexContext(ctx, tx, query)
}
// PrepareNamedContext returns an sqlx.NamedStmt
func (tx *Tx) PrepareNamedContext(ctx context.Context, query string) (*NamedStmt, error) {
return prepareNamedContext(ctx, tx, query)
}
// MustExecContext runs MustExecContext within a transaction.
// Any placeholder parameters are replaced with supplied args.
func (tx *Tx) MustExecContext(ctx context.Context, query string, args ...interface{}) sql.Result {
return MustExecContext(ctx, tx, query, args...)
}
// QueryxContext within a transaction and context.
// Any placeholder parameters are replaced with supplied args.
func (tx *Tx) QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) {
r, err := tx.Tx.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
return &Rows{Rows: r, unsafe: tx.unsafe, Mapper: tx.Mapper}, err
}
// SelectContext within a transaction and context.
// Any placeholder parameters are replaced with supplied args.
func (tx *Tx) SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
return SelectContext(ctx, tx, dest, query, args...)
}
// GetContext within a transaction and context.
// Any placeholder parameters are replaced with supplied args.
// An error is returned if the result set is empty.
func (tx *Tx) GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error {
return GetContext(ctx, tx, dest, query, args...)
}
// QueryRowxContext within a transaction and context.
// Any placeholder parameters are replaced with supplied args.
func (tx *Tx) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row {
rows, err := tx.Tx.QueryContext(ctx, query, args...)
return &Row{rows: rows, err: err, unsafe: tx.unsafe, Mapper: tx.Mapper}
}
// NamedExecContext using this Tx.
// Any named placeholder parameters are replaced with fields from arg.
func (tx *Tx) NamedExecContext(ctx context.Context, query string, arg interface{}) (sql.Result, error) {
return NamedExecContext(ctx, tx, query, arg)
}
// SelectContext using the prepared statement.
// Any placeholder parameters are replaced with supplied args.
func (s *Stmt) SelectContext(ctx context.Context, dest interface{}, args ...interface{}) error {
return SelectContext(ctx, &qStmt{s}, dest, "", args...)
}
// GetContext using the prepared statement.
// Any placeholder parameters are replaced with supplied args.
// An error is returned if the result set is empty.
func (s *Stmt) GetContext(ctx context.Context, dest interface{}, args ...interface{}) error {
return GetContext(ctx, &qStmt{s}, dest, "", args...)
}
// MustExecContext (panic) using this statement. Note that the query portion of
// the error output will be blank, as Stmt does not expose its query.
// Any placeholder parameters are replaced with supplied args.
func (s *Stmt) MustExecContext(ctx context.Context, args ...interface{}) sql.Result {
return MustExecContext(ctx, &qStmt{s}, "", args...)
}
// QueryRowxContext using this statement.
// Any placeholder parameters are replaced with supplied args.
func (s *Stmt) QueryRowxContext(ctx context.Context, args ...interface{}) *Row {
qs := &qStmt{s}
return qs.QueryRowxContext(ctx, "", args...)
}
// QueryxContext using this statement.
// Any placeholder parameters are replaced with supplied args.
func (s *Stmt) QueryxContext(ctx context.Context, args ...interface{}) (*Rows, error) {
qs := &qStmt{s}
return qs.QueryxContext(ctx, "", args...)
}
func (q *qStmt) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
return q.Stmt.QueryContext(ctx, args...)
}
func (q *qStmt) QueryxContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) {
r, err := q.Stmt.QueryContext(ctx, args...)
if err != nil {
return nil, err
}
return &Rows{Rows: r, unsafe: q.Stmt.unsafe, Mapper: q.Stmt.Mapper}, err
}
func (q *qStmt) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *Row {
rows, err := q.Stmt.QueryContext(ctx, args...)
return &Row{rows: rows, err: err, unsafe: q.Stmt.unsafe, Mapper: q.Stmt.Mapper}
}
func (q *qStmt) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
return q.Stmt.ExecContext(ctx, args...)
}

View File

@@ -36,6 +36,7 @@ Now you can easily test `myFunc` with a `FakeClock`:
```go ```go
func TestMyFunc(t *testing.T) { func TestMyFunc(t *testing.T) {
ctx := context.Background()
c := clockwork.NewFakeClock() c := clockwork.NewFakeClock()
// Start our sleepy function // Start our sleepy function
@@ -46,8 +47,12 @@ func TestMyFunc(t *testing.T) {
wg.Done() wg.Done()
}() }()
// Ensure we wait until myFunc is sleeping // Ensure we wait until myFunc is waiting on the clock.
c.BlockUntil(1) // Use a context to avoid blocking forever if something
// goes wrong.
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
c.BlockUntilContext(ctx, 1)
assertState() assertState()

19
vendor/github.com/jonboulle/clockwork/SECURITY.md generated vendored Normal file
View File

@@ -0,0 +1,19 @@
# Security Policy
If you have discovered a security vulnerability in this project, please report it
privately. **Do not disclose it as a public issue.** This gives me time to work with you
to fix the issue before public exposure, reducing the chance that the exploit will be
used before a patch is released.
You may submit the report in the following ways:
- send an email to ???@???; and/or
- send a [private vulnerability report](https://github.com/jonboulle/clockwork/security/advisories/new)
Please provide the following information in your report:
- A description of the vulnerability and its impact
- How to reproduce the issue
This project is maintained by a single maintainer on a reasonable-effort basis. As such,
please give me 90 days to work on a fix before public exposure.

View File

@@ -1,8 +1,10 @@
// Package clockwork contains a simple fake clock for Go.
package clockwork package clockwork
import ( import (
"context" "context"
"sort" "errors"
"slices"
"sync" "sync"
"time" "time"
) )
@@ -14,49 +16,18 @@ type Clock interface {
Sleep(d time.Duration) Sleep(d time.Duration)
Now() time.Time Now() time.Time
Since(t time.Time) time.Duration Since(t time.Time) time.Duration
Until(t time.Time) time.Duration
NewTicker(d time.Duration) Ticker NewTicker(d time.Duration) Ticker
NewTimer(d time.Duration) Timer NewTimer(d time.Duration) Timer
AfterFunc(d time.Duration, f func()) Timer AfterFunc(d time.Duration, f func()) Timer
} }
// FakeClock provides an interface for a clock which can be manually advanced
// through time.
//
// FakeClock maintains a list of "waiters," which consists of all callers
// waiting on the underlying clock (i.e. Tickers and Timers including callers of
// Sleep or After). Users can call BlockUntil to block until the clock has an
// expected number of waiters.
type FakeClock interface {
Clock
// Advance advances the FakeClock to a new point in time, ensuring any existing
// waiters are notified appropriately before returning.
Advance(d time.Duration)
// BlockUntil blocks until the FakeClock has the given number of waiters.
BlockUntil(waiters int)
}
// NewRealClock returns a Clock which simply delegates calls to the actual time // NewRealClock returns a Clock which simply delegates calls to the actual time
// package; it should be used by packages in production. // package; it should be used by packages in production.
func NewRealClock() Clock { func NewRealClock() Clock {
return &realClock{} return &realClock{}
} }
// NewFakeClock returns a FakeClock implementation which can be
// manually advanced through time for testing. The initial time of the
// FakeClock will be the current system time.
//
// Tests that require a deterministic time must use NewFakeClockAt.
func NewFakeClock() FakeClock {
return NewFakeClockAt(time.Now())
}
// NewFakeClockAt returns a FakeClock initialised at the given time.Time.
func NewFakeClockAt(t time.Time) FakeClock {
return &fakeClock{
time: t,
}
}
type realClock struct{} type realClock struct{}
func (rc *realClock) After(d time.Duration) <-chan time.Time { func (rc *realClock) After(d time.Duration) <-chan time.Time {
@@ -75,6 +46,10 @@ func (rc *realClock) Since(t time.Time) time.Duration {
return rc.Now().Sub(t) return rc.Now().Sub(t)
} }
func (rc *realClock) Until(t time.Time) time.Duration {
return t.Sub(rc.Now())
}
func (rc *realClock) NewTicker(d time.Duration) Ticker { func (rc *realClock) NewTicker(d time.Duration) Ticker {
return realTicker{time.NewTicker(d)} return realTicker{time.NewTicker(d)}
} }
@@ -87,7 +62,14 @@ func (rc *realClock) AfterFunc(d time.Duration, f func()) Timer {
return realTimer{time.AfterFunc(d, f)} return realTimer{time.AfterFunc(d, f)}
} }
type fakeClock struct { // FakeClock provides an interface for a clock which can be manually advanced
// through time.
//
// FakeClock maintains a list of "waiters," which consists of all callers
// waiting on the underlying clock (i.e. Tickers and Timers including callers of
// Sleep or After). Users can call BlockUntil to block until the clock has an
// expected number of waiters.
type FakeClock struct {
// l protects all attributes of the clock, including all attributes of all // l protects all attributes of the clock, including all attributes of all
// waiters and blockers. // waiters and blockers.
l sync.RWMutex l sync.RWMutex
@@ -96,11 +78,27 @@ type fakeClock struct {
time time.Time time time.Time
} }
// NewFakeClock returns a FakeClock implementation which can be
// manually advanced through time for testing. The initial time of the
// FakeClock will be the current system time.
//
// Tests that require a deterministic time must use NewFakeClockAt.
func NewFakeClock() *FakeClock {
return NewFakeClockAt(time.Now())
}
// NewFakeClockAt returns a FakeClock initialised at the given time.Time.
func NewFakeClockAt(t time.Time) *FakeClock {
return &FakeClock{
time: t,
}
}
// blocker is a caller of BlockUntil. // blocker is a caller of BlockUntil.
type blocker struct { type blocker struct {
count int count int
// ch is closed when the underlying clock has the specificed number of blockers. // ch is closed when the underlying clock has the specified number of blockers.
ch chan struct{} ch chan struct{}
} }
@@ -111,23 +109,23 @@ type expirer interface {
expire(now time.Time) (next *time.Duration) expire(now time.Time) (next *time.Duration)
// Get and set the expiration time. // Get and set the expiration time.
expiry() time.Time expiration() time.Time
setExpiry(time.Time) setExpiration(time.Time)
} }
// After mimics [time.After]; it waits for the given duration to elapse on the // After mimics [time.After]; it waits for the given duration to elapse on the
// fakeClock, then sends the current time on the returned channel. // fakeClock, then sends the current time on the returned channel.
func (fc *fakeClock) After(d time.Duration) <-chan time.Time { func (fc *FakeClock) After(d time.Duration) <-chan time.Time {
return fc.NewTimer(d).Chan() return fc.NewTimer(d).Chan()
} }
// Sleep blocks until the given duration has passed on the fakeClock. // Sleep blocks until the given duration has passed on the fakeClock.
func (fc *fakeClock) Sleep(d time.Duration) { func (fc *FakeClock) Sleep(d time.Duration) {
<-fc.After(d) <-fc.After(d)
} }
// Now returns the current time of the fakeClock // Now returns the current time of the fakeClock
func (fc *fakeClock) Now() time.Time { func (fc *FakeClock) Now() time.Time {
fc.l.RLock() fc.l.RLock()
defer fc.l.RUnlock() defer fc.l.RUnlock()
return fc.time return fc.time
@@ -135,61 +133,73 @@ func (fc *fakeClock) Now() time.Time {
// Since returns the duration that has passed since the given time on the // Since returns the duration that has passed since the given time on the
// fakeClock. // fakeClock.
func (fc *fakeClock) Since(t time.Time) time.Duration { func (fc *FakeClock) Since(t time.Time) time.Duration {
return fc.Now().Sub(t) return fc.Now().Sub(t)
} }
// Until returns the duration that has to pass from the given time on the fakeClock
// to reach the given time.
func (fc *FakeClock) Until(t time.Time) time.Duration {
return t.Sub(fc.Now())
}
// NewTicker returns a Ticker that will expire only after calls to // NewTicker returns a Ticker that will expire only after calls to
// fakeClock.Advance() have moved the clock past the given duration. // FakeClock.Advance() have moved the clock past the given duration.
func (fc *fakeClock) NewTicker(d time.Duration) Ticker { //
var ft *fakeTicker // The duration d must be greater than zero; if not, NewTicker will panic.
ft = &fakeTicker{ func (fc *FakeClock) NewTicker(d time.Duration) Ticker {
firer: newFirer(), // Maintain parity with
d: d, // https://cs.opensource.google/go/go/+/refs/tags/go1.20.3:src/time/tick.go;l=23-25
reset: func(d time.Duration) { fc.set(ft, d) }, if d <= 0 {
stop: func() { fc.stop(ft) }, panic(errors.New("non-positive interval for NewTicker"))
} }
fc.set(ft, d) ft := newFakeTicker(fc, d)
fc.l.Lock()
defer fc.l.Unlock()
fc.setExpirer(ft, d)
return ft return ft
} }
// NewTimer returns a Timer that will fire only after calls to // NewTimer returns a Timer that will fire only after calls to
// fakeClock.Advance() have moved the clock past the given duration. // fakeClock.Advance() have moved the clock past the given duration.
func (fc *fakeClock) NewTimer(d time.Duration) Timer { func (fc *FakeClock) NewTimer(d time.Duration) Timer {
return fc.newTimer(d, nil) t, _ := fc.newTimer(d, nil)
return t
} }
// AfterFunc mimics [time.AfterFunc]; it returns a Timer that will invoke the // AfterFunc mimics [time.AfterFunc]; it returns a Timer that will invoke the
// given function only after calls to fakeClock.Advance() have moved the clock // given function only after calls to fakeClock.Advance() have moved the clock
// past the given duration. // past the given duration.
func (fc *fakeClock) AfterFunc(d time.Duration, f func()) Timer { func (fc *FakeClock) AfterFunc(d time.Duration, f func()) Timer {
return fc.newTimer(d, f) t, _ := fc.newTimer(d, f)
return t
} }
// newTimer returns a new timer, using an optional afterFunc. // newTimer returns a new timer using an optional afterFunc and the time that
func (fc *fakeClock) newTimer(d time.Duration, afterfunc func()) *fakeTimer { // timer expires.
var ft *fakeTimer func (fc *FakeClock) newTimer(d time.Duration, afterfunc func()) (*fakeTimer, time.Time) {
ft = &fakeTimer{ ft := newFakeTimer(fc, afterfunc)
firer: newFirer(),
reset: func(d time.Duration) bool {
fc.l.Lock() fc.l.Lock()
defer fc.l.Unlock() defer fc.l.Unlock()
// fc.l must be held across the calls to stopExpirer & setExpirer.
stopped := fc.stopExpirer(ft)
fc.setExpirer(ft, d) fc.setExpirer(ft, d)
return stopped return ft, ft.expiration()
}, }
stop: func() bool { return fc.stop(ft) },
afterFunc: afterfunc, // newTimerAtTime is like newTimer, but uses a time instead of a duration.
} //
fc.set(ft, d) // It is used to ensure FakeClock's lock is held constant through calling
// fc.After(t.Sub(fc.Now())). It should not be exposed externally.
func (fc *FakeClock) newTimerAtTime(t time.Time, afterfunc func()) *fakeTimer {
ft := newFakeTimer(fc, afterfunc)
fc.l.Lock()
defer fc.l.Unlock()
fc.setExpirer(ft, t.Sub(fc.time))
return ft return ft
} }
// Advance advances fakeClock to a new point in time, ensuring waiters and // Advance advances fakeClock to a new point in time, ensuring waiters and
// blockers are notified appropriately before returning. // blockers are notified appropriately before returning.
func (fc *fakeClock) Advance(d time.Duration) { func (fc *FakeClock) Advance(d time.Duration) {
fc.l.Lock() fc.l.Lock()
defer fc.l.Unlock() defer fc.l.Unlock()
end := fc.time.Add(d) end := fc.time.Add(d)
@@ -198,38 +208,34 @@ func (fc *fakeClock) Advance(d time.Duration) {
// //
// We don't iterate because the callback of the waiter might register a new // We don't iterate because the callback of the waiter might register a new
// waiter, so the list of waiters might change as we execute this. // waiter, so the list of waiters might change as we execute this.
for len(fc.waiters) > 0 && !end.Before(fc.waiters[0].expiry()) { for len(fc.waiters) > 0 && !end.Before(fc.waiters[0].expiration()) {
w := fc.waiters[0] w := fc.waiters[0]
fc.waiters = fc.waiters[1:] fc.waiters = fc.waiters[1:]
// Use the waiter's expriation as the current time for this expiration. // Use the waiter's expiration as the current time for this expiration.
now := w.expiry() now := w.expiration()
fc.time = now fc.time = now
if d := w.expire(now); d != nil { if d := w.expire(now); d != nil {
// Set the new exipration if needed. // Set the new expiration if needed.
fc.setExpirer(w, *d) fc.setExpirer(w, *d)
} }
} }
fc.time = end fc.time = end
} }
// BlockUntil blocks until the fakeClock has the given number of waiters. // BlockUntil blocks until the FakeClock has the given number of waiters.
// //
// Prefer BlockUntilContext, which offers context cancellation to prevent // Prefer BlockUntilContext in new code, which offers context cancellation to
// deadlock. // prevent deadlock.
// //
// Deprecation warning: This function might be deprecated in later versions. // Deprecated: New code should prefer BlockUntilContext.
func (fc *fakeClock) BlockUntil(n int) { func (fc *FakeClock) BlockUntil(n int) {
b := fc.newBlocker(n) fc.BlockUntilContext(context.TODO(), n)
if b == nil {
return
}
<-b.ch
} }
// BlockUntilContext blocks until the fakeClock has the given number of waiters // BlockUntilContext blocks until the fakeClock has the given number of waiters
// or the context is cancelled. // or the context is cancelled.
func (fc *fakeClock) BlockUntilContext(ctx context.Context, n int) error { func (fc *FakeClock) BlockUntilContext(ctx context.Context, n int) error {
b := fc.newBlocker(n) b := fc.newBlocker(n)
if b == nil { if b == nil {
return nil return nil
@@ -243,7 +249,7 @@ func (fc *fakeClock) BlockUntilContext(ctx context.Context, n int) error {
} }
} }
func (fc *fakeClock) newBlocker(n int) *blocker { func (fc *FakeClock) newBlocker(n int) *blocker {
fc.l.Lock() fc.l.Lock()
defer fc.l.Unlock() defer fc.l.Unlock()
// Fast path: we already have >= n waiters. // Fast path: we already have >= n waiters.
@@ -260,7 +266,7 @@ func (fc *fakeClock) newBlocker(n int) *blocker {
} }
// stop stops an expirer, returning true if the expirer was stopped. // stop stops an expirer, returning true if the expirer was stopped.
func (fc *fakeClock) stop(e expirer) bool { func (fc *FakeClock) stop(e expirer) bool {
fc.l.Lock() fc.l.Lock()
defer fc.l.Unlock() defer fc.l.Unlock()
return fc.stopExpirer(e) return fc.stopExpirer(e)
@@ -269,81 +275,45 @@ func (fc *fakeClock) stop(e expirer) bool {
// stopExpirer stops an expirer, returning true if the expirer was stopped. // stopExpirer stops an expirer, returning true if the expirer was stopped.
// //
// The caller must hold fc.l. // The caller must hold fc.l.
func (fc *fakeClock) stopExpirer(e expirer) bool { func (fc *FakeClock) stopExpirer(e expirer) bool {
for i, t := range fc.waiters { idx := slices.Index(fc.waiters, e)
if t == e { if idx == -1 {
// Remove element, maintaining order. return false
copy(fc.waiters[i:], fc.waiters[i+1:]) }
// Remove element, maintaining order, setting inaccessible elements to nil so
// they can be garbage collected.
copy(fc.waiters[idx:], fc.waiters[idx+1:])
fc.waiters[len(fc.waiters)-1] = nil fc.waiters[len(fc.waiters)-1] = nil
fc.waiters = fc.waiters[:len(fc.waiters)-1] fc.waiters = fc.waiters[:len(fc.waiters)-1]
return true return true
}
}
return false
}
// set sets an expirer to expire at a future point in time.
func (fc *fakeClock) set(e expirer, d time.Duration) {
fc.l.Lock()
defer fc.l.Unlock()
fc.setExpirer(e, d)
} }
// setExpirer sets an expirer to expire at a future point in time. // setExpirer sets an expirer to expire at a future point in time.
// //
// The caller must hold fc.l. // The caller must hold fc.l.
func (fc *fakeClock) setExpirer(e expirer, d time.Duration) { func (fc *FakeClock) setExpirer(e expirer, d time.Duration) {
if d.Nanoseconds() <= 0 { if d.Nanoseconds() <= 0 {
// special case - trigger immediately, never reset. // Special case for timers with duration <= 0: trigger immediately, never
// reset.
// //
// TODO: Explain what cases this covers. // Tickers never get here, they panic if d is < 0.
e.expire(fc.time) e.expire(fc.time)
return return
} }
// Add the expirer to the set of waiters and notify any blockers. // Add the expirer to the set of waiters and notify any blockers.
e.setExpiry(fc.time.Add(d)) e.setExpiration(fc.time.Add(d))
fc.waiters = append(fc.waiters, e) fc.waiters = append(fc.waiters, e)
sort.Slice(fc.waiters, func(i int, j int) bool { slices.SortFunc(fc.waiters, func(a, b expirer) int {
return fc.waiters[i].expiry().Before(fc.waiters[j].expiry()) return a.expiration().Compare(b.expiration())
}) })
// Notify blockers of our new waiter. // Notify blockers of our new waiter.
var blocked []*blocker
count := len(fc.waiters) count := len(fc.waiters)
for _, b := range fc.blockers { fc.blockers = slices.DeleteFunc(fc.blockers, func(b *blocker) bool {
if b.count <= count { if b.count <= count {
close(b.ch) close(b.ch)
continue return true
} }
blocked = append(blocked, b) return false
} })
fc.blockers = blocked
}
// firer is used by fakeTimer and fakeTicker used to help implement expirer.
type firer struct {
// The channel associated with the firer, used to send expriation times.
c chan time.Time
// The time when the firer expires. Only meaningful if the firer is currently
// one of a fakeClock's waiters.
exp time.Time
}
func newFirer() firer {
return firer{c: make(chan time.Time, 1)}
}
func (f *firer) Chan() <-chan time.Time {
return f.c
}
// expiry implements expirer.
func (f *firer) expiry() time.Time {
return f.exp
}
// setExpiry implements expirer.
func (f *firer) setExpiry(t time.Time) {
f.exp = t
} }

View File

@@ -2,24 +2,168 @@ package clockwork
import ( import (
"context" "context"
"fmt"
"sync"
"time"
) )
// contextKey is private to this package so we can ensure uniqueness here. This // contextKey is private to this package so we can ensure uniqueness here. This
// type identifies context values provided by this package. // type identifies context values provided by this package.
type contextKey string type contextKey string
// keyClock provides a clock for injecting during tests. If absent, a real clock should be used. // keyClock provides a clock for injecting during tests. If absent, a real clock
// should be used.
var keyClock = contextKey("clock") // clockwork.Clock var keyClock = contextKey("clock") // clockwork.Clock
// AddToContext creates a derived context that references the specified clock. // AddToContext creates a derived context that references the specified clock.
//
// Be aware this doesn't change the behavior of standard library functions, such
// as [context.WithTimeout] or [context.WithDeadline]. For this reason, users
// should prefer passing explicit [clockwork.Clock] variables rather can passing
// the clock via the context.
func AddToContext(ctx context.Context, clock Clock) context.Context { func AddToContext(ctx context.Context, clock Clock) context.Context {
return context.WithValue(ctx, keyClock, clock) return context.WithValue(ctx, keyClock, clock)
} }
// FromContext extracts a clock from the context. If not present, a real clock is returned. // FromContext extracts a clock from the context. If not present, a real clock
// is returned.
func FromContext(ctx context.Context) Clock { func FromContext(ctx context.Context) Clock {
if clock, ok := ctx.Value(keyClock).(Clock); ok { if clock, ok := ctx.Value(keyClock).(Clock); ok {
return clock return clock
} }
return NewRealClock() return NewRealClock()
} }
// ErrFakeClockDeadlineExceeded is the error returned by [context.Context] when
// the deadline passes on a context which uses a [FakeClock].
//
// It wraps a [context.DeadlineExceeded] error, i.e.:
//
// // The following is true for any Context whose deadline has been exceeded,
// // including contexts made with clockwork.WithDeadline or clockwork.WithTimeout.
//
// errors.Is(ctx.Err(), context.DeadlineExceeded)
//
// // The following can only be true for contexts made
// // with clockwork.WithDeadline or clockwork.WithTimeout.
//
// errors.Is(ctx.Err(), clockwork.ErrFakeClockDeadlineExceeded)
var ErrFakeClockDeadlineExceeded error = fmt.Errorf("clockwork.FakeClock: %w", context.DeadlineExceeded)
// WithDeadline returns a context with a deadline based on a [FakeClock].
//
// The returned context ignores parent cancelation if the parent was cancelled
// with a [context.DeadlineExceeded] error. Any other error returned by the
// parent is treated normally, cancelling the returned context.
//
// If the parent is cancelled with a [context.DeadlineExceeded] error, the only
// way to then cancel the returned context is by calling the returned
// context.CancelFunc.
func WithDeadline(parent context.Context, clock Clock, t time.Time) (context.Context, context.CancelFunc) {
if fc, ok := clock.(*FakeClock); ok {
return newFakeClockContext(parent, t, fc.newTimerAtTime(t, nil).Chan())
}
return context.WithDeadline(parent, t)
}
// WithTimeout returns a context with a timeout based on a [FakeClock].
//
// The returned context follows the same behaviors as [WithDeadline].
func WithTimeout(parent context.Context, clock Clock, d time.Duration) (context.Context, context.CancelFunc) {
if fc, ok := clock.(*FakeClock); ok {
t, deadline := fc.newTimer(d, nil)
return newFakeClockContext(parent, deadline, t.Chan())
}
return context.WithTimeout(parent, d)
}
// fakeClockContext implements context.Context, using a fake clock for its
// deadline.
//
// It ignores parent cancellation if the parent is cancelled with
// context.DeadlineExceeded.
type fakeClockContext struct {
parent context.Context
deadline time.Time // The user-facing deadline based on the fake clock's time.
// Tracks timeout/deadline cancellation.
timerDone <-chan time.Time
// Tracks manual calls to the cancel function.
cancel func() // Closes cancelCalled wrapped in a sync.Once.
cancelCalled chan struct{}
// The user-facing data from the context.Context interface.
ctxDone chan struct{} // Returned by Done().
err error // nil until ctxDone is ready to be closed.
}
func newFakeClockContext(parent context.Context, deadline time.Time, timer <-chan time.Time) (context.Context, context.CancelFunc) {
cancelCalled := make(chan struct{})
ctx := &fakeClockContext{
parent: parent,
deadline: deadline,
timerDone: timer,
cancelCalled: cancelCalled,
ctxDone: make(chan struct{}),
cancel: sync.OnceFunc(func() {
close(cancelCalled)
}),
}
ready := make(chan struct{}, 1)
go ctx.runCancel(ready)
<-ready // Wait until the cancellation goroutine is running.
return ctx, ctx.cancel
}
func (c *fakeClockContext) Deadline() (time.Time, bool) {
return c.deadline, true
}
func (c *fakeClockContext) Done() <-chan struct{} {
return c.ctxDone
}
func (c *fakeClockContext) Err() error {
<-c.Done() // Don't return the error before it is ready.
return c.err
}
func (c *fakeClockContext) Value(key any) any {
return c.parent.Value(key)
}
// runCancel runs the fakeClockContext's cancel goroutine and returns the
// fakeClockContext's cancel function.
//
// fakeClockContext is then cancelled when any of the following occur:
//
// - The fakeClockContext.done channel is closed by its timer.
// - The returned CancelFunc is executed.
// - The fakeClockContext's parent context is cancelled with an error other
// than context.DeadlineExceeded.
func (c *fakeClockContext) runCancel(ready chan struct{}) {
parentDone := c.parent.Done()
// Close ready when done, just in case the ready signal races with other
// branches of our select statement below.
defer close(ready)
for c.err == nil {
select {
case <-c.timerDone:
c.err = ErrFakeClockDeadlineExceeded
case <-c.cancelCalled:
c.err = context.Canceled
case <-parentDone:
c.err = c.parent.Err()
case ready <- struct{}{}:
// Signals the cancellation goroutine has begun, in an attempt to minimize
// race conditions related to goroutine startup time.
ready = nil // This case statement can only fire once.
}
}
close(c.ctxDone)
return
}

View File

@@ -19,7 +19,12 @@ func (r realTicker) Chan() <-chan time.Time {
} }
type fakeTicker struct { type fakeTicker struct {
firer // The channel associated with the firer, used to send expiration times.
c chan time.Time
// The time when the ticker expires. Only meaningful if the ticker is currently
// one of a FakeClock's waiters.
exp time.Time
// reset and stop provide the implementation of the respective exported // reset and stop provide the implementation of the respective exported
// functions. // functions.
@@ -30,13 +35,27 @@ type fakeTicker struct {
d time.Duration d time.Duration
} }
func (f *fakeTicker) Reset(d time.Duration) { func newFakeTicker(fc *FakeClock, d time.Duration) *fakeTicker {
f.reset(d) var ft *fakeTicker
ft = &fakeTicker{
c: make(chan time.Time, 1),
d: d,
reset: func(d time.Duration) {
fc.l.Lock()
defer fc.l.Unlock()
ft.d = d
fc.setExpirer(ft, d)
},
stop: func() { fc.stop(ft) },
}
return ft
} }
func (f *fakeTicker) Stop() { func (f *fakeTicker) Chan() <-chan time.Time { return f.c }
f.stop()
} func (f *fakeTicker) Reset(d time.Duration) { f.reset(d) }
func (f *fakeTicker) Stop() { f.stop() }
func (f *fakeTicker) expire(now time.Time) *time.Duration { func (f *fakeTicker) expire(now time.Time) *time.Duration {
// Never block on expiration. // Never block on expiration.
@@ -46,3 +65,7 @@ func (f *fakeTicker) expire(now time.Time) *time.Duration {
} }
return &f.d return &f.d
} }
func (f *fakeTicker) expiration() time.Time { return f.exp }
func (f *fakeTicker) setExpiration(t time.Time) { f.exp = t }

View File

@@ -18,9 +18,14 @@ func (r realTimer) Chan() <-chan time.Time {
} }
type fakeTimer struct { type fakeTimer struct {
firer // The channel associated with the firer, used to send expiration times.
c chan time.Time
// reset and stop provide the implmenetation of the respective exported // The time when the firer expires. Only meaningful if the firer is currently
// one of a FakeClock's waiters.
exp time.Time
// reset and stop provide the implementation of the respective exported
// functions. // functions.
reset func(d time.Duration) bool reset func(d time.Duration) bool
stop func() bool stop func() bool
@@ -30,13 +35,30 @@ type fakeTimer struct {
afterFunc func() afterFunc func()
} }
func (f *fakeTimer) Reset(d time.Duration) bool { func newFakeTimer(fc *FakeClock, afterfunc func()) *fakeTimer {
return f.reset(d) var ft *fakeTimer
ft = &fakeTimer{
c: make(chan time.Time, 1),
reset: func(d time.Duration) bool {
fc.l.Lock()
defer fc.l.Unlock()
// fc.l must be held across the calls to stopExpirer & setExpirer.
stopped := fc.stopExpirer(ft)
fc.setExpirer(ft, d)
return stopped
},
stop: func() bool { return fc.stop(ft) },
afterFunc: afterfunc,
}
return ft
} }
func (f *fakeTimer) Stop() bool { func (f *fakeTimer) Chan() <-chan time.Time { return f.c }
return f.stop()
} func (f *fakeTimer) Reset(d time.Duration) bool { return f.reset(d) }
func (f *fakeTimer) Stop() bool { return f.stop() }
func (f *fakeTimer) expire(now time.Time) *time.Duration { func (f *fakeTimer) expire(now time.Time) *time.Duration {
if f.afterFunc != nil { if f.afterFunc != nil {
@@ -51,3 +73,7 @@ func (f *fakeTimer) expire(now time.Time) *time.Duration {
} }
return nil return nil
} }
func (f *fakeTimer) expiration() time.Time { return f.exp }
func (f *fakeTimer) setExpiration(t time.Time) { f.exp = t }

View File

@@ -10,3 +10,6 @@ lint.log
# Profiling output # Profiling output
*.prof *.prof
# Output of fossa analyzer
/fossa

View File

@@ -1,27 +0,0 @@
sudo: false
language: go
go_import_path: go.uber.org/atomic
env:
global:
- GO111MODULE=on
matrix:
include:
- go: oldstable
- go: stable
env: LINT=1
cache:
directories:
- vendor
before_install:
- go version
script:
- test -z "$LINT" || make lint
- make cover
after_success:
- bash <(curl -s https://codecov.io/bash)

View File

@@ -4,6 +4,47 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.11.0] - 2023-05-02
### Fixed
- Fix initialization of `Value` wrappers.
### Added
- Add `String` method to `atomic.Pointer[T]` type allowing users to safely print
underlying values of pointers.
[1.11.0]: https://github.com/uber-go/atomic/compare/v1.10.0...v1.11.0
## [1.10.0] - 2022-08-11
### Added
- Add `atomic.Float32` type for atomic operations on `float32`.
- Add `CompareAndSwap` and `Swap` methods to `atomic.String`, `atomic.Error`,
and `atomic.Value`.
- Add generic `atomic.Pointer[T]` type for atomic operations on pointers of any
type. This is present only for Go 1.18 or higher, and is a drop-in for
replacement for the standard library's `sync/atomic.Pointer` type.
### Changed
- Deprecate `CAS` methods on all types in favor of corresponding
`CompareAndSwap` methods.
Thanks to @eNV25 and @icpd for their contributions to this release.
[1.10.0]: https://github.com/uber-go/atomic/compare/v1.9.0...v1.10.0
## [1.9.0] - 2021-07-15
### Added
- Add `Float64.Swap` to match int atomic operations.
- Add `atomic.Time` type for atomic operations on `time.Time` values.
[1.9.0]: https://github.com/uber-go/atomic/compare/v1.8.0...v1.9.0
## [1.8.0] - 2021-06-09
### Added
- Add `atomic.Uintptr` type for atomic operations on `uintptr` values.
- Add `atomic.UnsafePointer` type for atomic operations on `unsafe.Pointer` values.
[1.8.0]: https://github.com/uber-go/atomic/compare/v1.7.0...v1.8.0
## [1.7.0] - 2020-09-14 ## [1.7.0] - 2020-09-14
### Added ### Added
- Support JSON serialization and deserialization of primitive atomic types. - Support JSON serialization and deserialization of primitive atomic types.
@@ -15,32 +56,46 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Removed ### Removed
- Remove dependency on `golang.org/x/{lint, tools}`. - Remove dependency on `golang.org/x/{lint, tools}`.
[1.7.0]: https://github.com/uber-go/atomic/compare/v1.6.0...v1.7.0
## [1.6.0] - 2020-02-24 ## [1.6.0] - 2020-02-24
### Changed ### Changed
- Drop library dependency on `golang.org/x/{lint, tools}`. - Drop library dependency on `golang.org/x/{lint, tools}`.
[1.6.0]: https://github.com/uber-go/atomic/compare/v1.5.1...v1.6.0
## [1.5.1] - 2019-11-19 ## [1.5.1] - 2019-11-19
- Fix bug where `Bool.CAS` and `Bool.Toggle` do work correctly together - Fix bug where `Bool.CAS` and `Bool.Toggle` do work correctly together
causing `CAS` to fail even though the old value matches. causing `CAS` to fail even though the old value matches.
[1.5.1]: https://github.com/uber-go/atomic/compare/v1.5.0...v1.5.1
## [1.5.0] - 2019-10-29 ## [1.5.0] - 2019-10-29
### Changed ### Changed
- With Go modules, only the `go.uber.org/atomic` import path is supported now. - With Go modules, only the `go.uber.org/atomic` import path is supported now.
If you need to use the old import path, please add a `replace` directive to If you need to use the old import path, please add a `replace` directive to
your `go.mod`. your `go.mod`.
[1.5.0]: https://github.com/uber-go/atomic/compare/v1.4.0...v1.5.0
## [1.4.0] - 2019-05-01 ## [1.4.0] - 2019-05-01
### Added ### Added
- Add `atomic.Error` type for atomic operations on `error` values. - Add `atomic.Error` type for atomic operations on `error` values.
[1.4.0]: https://github.com/uber-go/atomic/compare/v1.3.2...v1.4.0
## [1.3.2] - 2018-05-02 ## [1.3.2] - 2018-05-02
### Added ### Added
- Add `atomic.Duration` type for atomic operations on `time.Duration` values. - Add `atomic.Duration` type for atomic operations on `time.Duration` values.
[1.3.2]: https://github.com/uber-go/atomic/compare/v1.3.1...v1.3.2
## [1.3.1] - 2017-11-14 ## [1.3.1] - 2017-11-14
### Fixed ### Fixed
- Revert optimization for `atomic.String.Store("")` which caused data races. - Revert optimization for `atomic.String.Store("")` which caused data races.
[1.3.1]: https://github.com/uber-go/atomic/compare/v1.3.0...v1.3.1
## [1.3.0] - 2017-11-13 ## [1.3.0] - 2017-11-13
### Added ### Added
- Add `atomic.Bool.CAS` for compare-and-swap semantics on bools. - Add `atomic.Bool.CAS` for compare-and-swap semantics on bools.
@@ -48,10 +103,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Optimize `atomic.String.Store("")` by avoiding an allocation. - Optimize `atomic.String.Store("")` by avoiding an allocation.
[1.3.0]: https://github.com/uber-go/atomic/compare/v1.2.0...v1.3.0
## [1.2.0] - 2017-04-12 ## [1.2.0] - 2017-04-12
### Added ### Added
- Shadow `atomic.Value` from `sync/atomic`. - Shadow `atomic.Value` from `sync/atomic`.
[1.2.0]: https://github.com/uber-go/atomic/compare/v1.1.0...v1.2.0
## [1.1.0] - 2017-03-10 ## [1.1.0] - 2017-03-10
### Added ### Added
- Add atomic `Float64` type. - Add atomic `Float64` type.
@@ -59,18 +118,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Support new `go.uber.org/atomic` import path. - Support new `go.uber.org/atomic` import path.
[1.1.0]: https://github.com/uber-go/atomic/compare/v1.0.0...v1.1.0
## [1.0.0] - 2016-07-18 ## [1.0.0] - 2016-07-18
- Initial release. - Initial release.
[1.7.0]: https://github.com/uber-go/atomic/compare/v1.6.0...v1.7.0
[1.6.0]: https://github.com/uber-go/atomic/compare/v1.5.1...v1.6.0
[1.5.1]: https://github.com/uber-go/atomic/compare/v1.5.0...v1.5.1
[1.5.0]: https://github.com/uber-go/atomic/compare/v1.4.0...v1.5.0
[1.4.0]: https://github.com/uber-go/atomic/compare/v1.3.2...v1.4.0
[1.3.2]: https://github.com/uber-go/atomic/compare/v1.3.1...v1.3.2
[1.3.1]: https://github.com/uber-go/atomic/compare/v1.3.0...v1.3.1
[1.3.0]: https://github.com/uber-go/atomic/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/uber-go/atomic/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/uber-go/atomic/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/uber-go/atomic/releases/tag/v1.0.0 [1.0.0]: https://github.com/uber-go/atomic/releases/tag/v1.0.0

1
vendor/go.uber.org/atomic/Makefile generated vendored
View File

@@ -69,6 +69,7 @@ generate: $(GEN_ATOMICINT) $(GEN_ATOMICWRAPPER)
generatenodirty: generatenodirty:
@[ -z "$$(git status --porcelain)" ] || ( \ @[ -z "$$(git status --porcelain)" ] || ( \
echo "Working tree is dirty. Commit your changes first."; \ echo "Working tree is dirty. Commit your changes first."; \
git status; \
exit 1 ) exit 1 )
@make generate @make generate
@status=$$(git status --porcelain); \ @status=$$(git status --porcelain); \

View File

@@ -55,8 +55,8 @@ Released under the [MIT License](LICENSE.txt).
[doc-img]: https://godoc.org/github.com/uber-go/atomic?status.svg [doc-img]: https://godoc.org/github.com/uber-go/atomic?status.svg
[doc]: https://godoc.org/go.uber.org/atomic [doc]: https://godoc.org/go.uber.org/atomic
[ci-img]: https://travis-ci.com/uber-go/atomic.svg?branch=master [ci-img]: https://github.com/uber-go/atomic/actions/workflows/go.yml/badge.svg
[ci]: https://travis-ci.com/uber-go/atomic [ci]: https://github.com/uber-go/atomic/actions/workflows/go.yml
[cov-img]: https://codecov.io/gh/uber-go/atomic/branch/master/graph/badge.svg [cov-img]: https://codecov.io/gh/uber-go/atomic/branch/master/graph/badge.svg
[cov]: https://codecov.io/gh/uber-go/atomic [cov]: https://codecov.io/gh/uber-go/atomic
[reportcard-img]: https://goreportcard.com/badge/go.uber.org/atomic [reportcard-img]: https://goreportcard.com/badge/go.uber.org/atomic

27
vendor/go.uber.org/atomic/bool.go generated vendored
View File

@@ -1,6 +1,6 @@
// @generated Code generated by gen-atomicwrapper. // @generated Code generated by gen-atomicwrapper.
// Copyright (c) 2020 Uber Technologies, Inc. // Copyright (c) 2020-2023 Uber Technologies, Inc.
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -36,10 +36,10 @@ type Bool struct {
var _zeroBool bool var _zeroBool bool
// NewBool creates a new Bool. // NewBool creates a new Bool.
func NewBool(v bool) *Bool { func NewBool(val bool) *Bool {
x := &Bool{} x := &Bool{}
if v != _zeroBool { if val != _zeroBool {
x.Store(v) x.Store(val)
} }
return x return x
} }
@@ -50,19 +50,26 @@ func (x *Bool) Load() bool {
} }
// Store atomically stores the passed bool. // Store atomically stores the passed bool.
func (x *Bool) Store(v bool) { func (x *Bool) Store(val bool) {
x.v.Store(boolToInt(v)) x.v.Store(boolToInt(val))
} }
// CAS is an atomic compare-and-swap for bool values. // CAS is an atomic compare-and-swap for bool values.
func (x *Bool) CAS(o, n bool) bool { //
return x.v.CAS(boolToInt(o), boolToInt(n)) // Deprecated: Use CompareAndSwap.
func (x *Bool) CAS(old, new bool) (swapped bool) {
return x.CompareAndSwap(old, new)
}
// CompareAndSwap is an atomic compare-and-swap for bool values.
func (x *Bool) CompareAndSwap(old, new bool) (swapped bool) {
return x.v.CompareAndSwap(boolToInt(old), boolToInt(new))
} }
// Swap atomically stores the given bool and returns the old // Swap atomically stores the given bool and returns the old
// value. // value.
func (x *Bool) Swap(o bool) bool { func (x *Bool) Swap(val bool) (old bool) {
return truthy(x.v.Swap(boolToInt(o))) return truthy(x.v.Swap(boolToInt(val)))
} }
// MarshalJSON encodes the wrapped bool into JSON. // MarshalJSON encodes the wrapped bool into JSON.

View File

@@ -38,7 +38,7 @@ func boolToInt(b bool) uint32 {
} }
// Toggle atomically negates the Boolean and returns the previous value. // Toggle atomically negates the Boolean and returns the previous value.
func (b *Bool) Toggle() bool { func (b *Bool) Toggle() (old bool) {
for { for {
old := b.Load() old := b.Load()
if b.CAS(old, !old) { if b.CAS(old, !old) {

View File

@@ -1,6 +1,6 @@
// @generated Code generated by gen-atomicwrapper. // @generated Code generated by gen-atomicwrapper.
// Copyright (c) 2020 Uber Technologies, Inc. // Copyright (c) 2020-2023 Uber Technologies, Inc.
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -37,10 +37,10 @@ type Duration struct {
var _zeroDuration time.Duration var _zeroDuration time.Duration
// NewDuration creates a new Duration. // NewDuration creates a new Duration.
func NewDuration(v time.Duration) *Duration { func NewDuration(val time.Duration) *Duration {
x := &Duration{} x := &Duration{}
if v != _zeroDuration { if val != _zeroDuration {
x.Store(v) x.Store(val)
} }
return x return x
} }
@@ -51,19 +51,26 @@ func (x *Duration) Load() time.Duration {
} }
// Store atomically stores the passed time.Duration. // Store atomically stores the passed time.Duration.
func (x *Duration) Store(v time.Duration) { func (x *Duration) Store(val time.Duration) {
x.v.Store(int64(v)) x.v.Store(int64(val))
} }
// CAS is an atomic compare-and-swap for time.Duration values. // CAS is an atomic compare-and-swap for time.Duration values.
func (x *Duration) CAS(o, n time.Duration) bool { //
return x.v.CAS(int64(o), int64(n)) // Deprecated: Use CompareAndSwap.
func (x *Duration) CAS(old, new time.Duration) (swapped bool) {
return x.CompareAndSwap(old, new)
}
// CompareAndSwap is an atomic compare-and-swap for time.Duration values.
func (x *Duration) CompareAndSwap(old, new time.Duration) (swapped bool) {
return x.v.CompareAndSwap(int64(old), int64(new))
} }
// Swap atomically stores the given time.Duration and returns the old // Swap atomically stores the given time.Duration and returns the old
// value. // value.
func (x *Duration) Swap(o time.Duration) time.Duration { func (x *Duration) Swap(val time.Duration) (old time.Duration) {
return time.Duration(x.v.Swap(int64(o))) return time.Duration(x.v.Swap(int64(val)))
} }
// MarshalJSON encodes the wrapped time.Duration into JSON. // MarshalJSON encodes the wrapped time.Duration into JSON.

View File

@@ -25,13 +25,13 @@ import "time"
//go:generate bin/gen-atomicwrapper -name=Duration -type=time.Duration -wrapped=Int64 -pack=int64 -unpack=time.Duration -cas -swap -json -imports time -file=duration.go //go:generate bin/gen-atomicwrapper -name=Duration -type=time.Duration -wrapped=Int64 -pack=int64 -unpack=time.Duration -cas -swap -json -imports time -file=duration.go
// Add atomically adds to the wrapped time.Duration and returns the new value. // Add atomically adds to the wrapped time.Duration and returns the new value.
func (d *Duration) Add(n time.Duration) time.Duration { func (d *Duration) Add(delta time.Duration) time.Duration {
return time.Duration(d.v.Add(int64(n))) return time.Duration(d.v.Add(int64(delta)))
} }
// Sub atomically subtracts from the wrapped time.Duration and returns the new value. // Sub atomically subtracts from the wrapped time.Duration and returns the new value.
func (d *Duration) Sub(n time.Duration) time.Duration { func (d *Duration) Sub(delta time.Duration) time.Duration {
return time.Duration(d.v.Sub(int64(n))) return time.Duration(d.v.Sub(int64(delta)))
} }
// String encodes the wrapped value as a string. // String encodes the wrapped value as a string.

33
vendor/go.uber.org/atomic/error.go generated vendored
View File

@@ -1,6 +1,6 @@
// @generated Code generated by gen-atomicwrapper. // @generated Code generated by gen-atomicwrapper.
// Copyright (c) 2020 Uber Technologies, Inc. // Copyright (c) 2020-2023 Uber Technologies, Inc.
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -32,10 +32,10 @@ type Error struct {
var _zeroError error var _zeroError error
// NewError creates a new Error. // NewError creates a new Error.
func NewError(v error) *Error { func NewError(val error) *Error {
x := &Error{} x := &Error{}
if v != _zeroError { if val != _zeroError {
x.Store(v) x.Store(val)
} }
return x return x
} }
@@ -46,6 +46,27 @@ func (x *Error) Load() error {
} }
// Store atomically stores the passed error. // Store atomically stores the passed error.
func (x *Error) Store(v error) { func (x *Error) Store(val error) {
x.v.Store(packError(v)) x.v.Store(packError(val))
}
// CompareAndSwap is an atomic compare-and-swap for error values.
func (x *Error) CompareAndSwap(old, new error) (swapped bool) {
if x.v.CompareAndSwap(packError(old), packError(new)) {
return true
}
if old == _zeroError {
// If the old value is the empty value, then it's possible the
// underlying Value hasn't been set and is nil, so retry with nil.
return x.v.CompareAndSwap(nil, packError(new))
}
return false
}
// Swap atomically stores the given error and returns the old
// value.
func (x *Error) Swap(val error) (old error) {
return unpackError(x.v.Swap(packError(val)))
} }

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2020 Uber Technologies, Inc. // Copyright (c) 2020-2022 Uber Technologies, Inc.
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -23,7 +23,7 @@ package atomic
// atomic.Value panics on nil inputs, or if the underlying type changes. // atomic.Value panics on nil inputs, or if the underlying type changes.
// Stabilize by always storing a custom struct that we control. // Stabilize by always storing a custom struct that we control.
//go:generate bin/gen-atomicwrapper -name=Error -type=error -wrapped=Value -pack=packError -unpack=unpackError -file=error.go //go:generate bin/gen-atomicwrapper -name=Error -type=error -wrapped=Value -pack=packError -unpack=unpackError -compareandswap -swap -file=error.go
type packedError struct{ Value error } type packedError struct{ Value error }

77
vendor/go.uber.org/atomic/float32.go generated vendored Normal file
View File

@@ -0,0 +1,77 @@
// @generated Code generated by gen-atomicwrapper.
// Copyright (c) 2020-2023 Uber Technologies, Inc.
//
// 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.
package atomic
import (
"encoding/json"
"math"
)
// Float32 is an atomic type-safe wrapper for float32 values.
type Float32 struct {
_ nocmp // disallow non-atomic comparison
v Uint32
}
var _zeroFloat32 float32
// NewFloat32 creates a new Float32.
func NewFloat32(val float32) *Float32 {
x := &Float32{}
if val != _zeroFloat32 {
x.Store(val)
}
return x
}
// Load atomically loads the wrapped float32.
func (x *Float32) Load() float32 {
return math.Float32frombits(x.v.Load())
}
// Store atomically stores the passed float32.
func (x *Float32) Store(val float32) {
x.v.Store(math.Float32bits(val))
}
// Swap atomically stores the given float32 and returns the old
// value.
func (x *Float32) Swap(val float32) (old float32) {
return math.Float32frombits(x.v.Swap(math.Float32bits(val)))
}
// MarshalJSON encodes the wrapped float32 into JSON.
func (x *Float32) MarshalJSON() ([]byte, error) {
return json.Marshal(x.Load())
}
// UnmarshalJSON decodes a float32 from JSON.
func (x *Float32) UnmarshalJSON(b []byte) error {
var v float32
if err := json.Unmarshal(b, &v); err != nil {
return err
}
x.Store(v)
return nil
}

76
vendor/go.uber.org/atomic/float32_ext.go generated vendored Normal file
View File

@@ -0,0 +1,76 @@
// Copyright (c) 2020-2022 Uber Technologies, Inc.
//
// 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.
package atomic
import (
"math"
"strconv"
)
//go:generate bin/gen-atomicwrapper -name=Float32 -type=float32 -wrapped=Uint32 -pack=math.Float32bits -unpack=math.Float32frombits -swap -json -imports math -file=float32.go
// Add atomically adds to the wrapped float32 and returns the new value.
func (f *Float32) Add(delta float32) float32 {
for {
old := f.Load()
new := old + delta
if f.CAS(old, new) {
return new
}
}
}
// Sub atomically subtracts from the wrapped float32 and returns the new value.
func (f *Float32) Sub(delta float32) float32 {
return f.Add(-delta)
}
// CAS is an atomic compare-and-swap for float32 values.
//
// Deprecated: Use CompareAndSwap
func (f *Float32) CAS(old, new float32) (swapped bool) {
return f.CompareAndSwap(old, new)
}
// CompareAndSwap is an atomic compare-and-swap for float32 values.
//
// Note: CompareAndSwap handles NaN incorrectly. NaN != NaN using Go's inbuilt operators
// but CompareAndSwap allows a stored NaN to compare equal to a passed in NaN.
// This avoids typical CompareAndSwap loops from blocking forever, e.g.,
//
// for {
// old := atom.Load()
// new = f(old)
// if atom.CompareAndSwap(old, new) {
// break
// }
// }
//
// If CompareAndSwap did not match NaN to match, then the above would loop forever.
func (f *Float32) CompareAndSwap(old, new float32) (swapped bool) {
return f.v.CompareAndSwap(math.Float32bits(old), math.Float32bits(new))
}
// String encodes the wrapped value as a string.
func (f *Float32) String() string {
// 'g' is the behavior for floats with %v.
return strconv.FormatFloat(float64(f.Load()), 'g', -1, 32)
}

19
vendor/go.uber.org/atomic/float64.go generated vendored
View File

@@ -1,6 +1,6 @@
// @generated Code generated by gen-atomicwrapper. // @generated Code generated by gen-atomicwrapper.
// Copyright (c) 2020 Uber Technologies, Inc. // Copyright (c) 2020-2023 Uber Technologies, Inc.
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -37,10 +37,10 @@ type Float64 struct {
var _zeroFloat64 float64 var _zeroFloat64 float64
// NewFloat64 creates a new Float64. // NewFloat64 creates a new Float64.
func NewFloat64(v float64) *Float64 { func NewFloat64(val float64) *Float64 {
x := &Float64{} x := &Float64{}
if v != _zeroFloat64 { if val != _zeroFloat64 {
x.Store(v) x.Store(val)
} }
return x return x
} }
@@ -51,13 +51,14 @@ func (x *Float64) Load() float64 {
} }
// Store atomically stores the passed float64. // Store atomically stores the passed float64.
func (x *Float64) Store(v float64) { func (x *Float64) Store(val float64) {
x.v.Store(math.Float64bits(v)) x.v.Store(math.Float64bits(val))
} }
// CAS is an atomic compare-and-swap for float64 values. // Swap atomically stores the given float64 and returns the old
func (x *Float64) CAS(o, n float64) bool { // value.
return x.v.CAS(math.Float64bits(o), math.Float64bits(n)) func (x *Float64) Swap(val float64) (old float64) {
return math.Float64frombits(x.v.Swap(math.Float64bits(val)))
} }
// MarshalJSON encodes the wrapped float64 into JSON. // MarshalJSON encodes the wrapped float64 into JSON.

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2020 Uber Technologies, Inc. // Copyright (c) 2020-2022 Uber Technologies, Inc.
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -20,15 +20,18 @@
package atomic package atomic
import "strconv" import (
"math"
"strconv"
)
//go:generate bin/gen-atomicwrapper -name=Float64 -type=float64 -wrapped=Uint64 -pack=math.Float64bits -unpack=math.Float64frombits -cas -json -imports math -file=float64.go //go:generate bin/gen-atomicwrapper -name=Float64 -type=float64 -wrapped=Uint64 -pack=math.Float64bits -unpack=math.Float64frombits -swap -json -imports math -file=float64.go
// Add atomically adds to the wrapped float64 and returns the new value. // Add atomically adds to the wrapped float64 and returns the new value.
func (f *Float64) Add(s float64) float64 { func (f *Float64) Add(delta float64) float64 {
for { for {
old := f.Load() old := f.Load()
new := old + s new := old + delta
if f.CAS(old, new) { if f.CAS(old, new) {
return new return new
} }
@@ -36,8 +39,34 @@ func (f *Float64) Add(s float64) float64 {
} }
// Sub atomically subtracts from the wrapped float64 and returns the new value. // Sub atomically subtracts from the wrapped float64 and returns the new value.
func (f *Float64) Sub(s float64) float64 { func (f *Float64) Sub(delta float64) float64 {
return f.Add(-s) return f.Add(-delta)
}
// CAS is an atomic compare-and-swap for float64 values.
//
// Deprecated: Use CompareAndSwap
func (f *Float64) CAS(old, new float64) (swapped bool) {
return f.CompareAndSwap(old, new)
}
// CompareAndSwap is an atomic compare-and-swap for float64 values.
//
// Note: CompareAndSwap handles NaN incorrectly. NaN != NaN using Go's inbuilt operators
// but CompareAndSwap allows a stored NaN to compare equal to a passed in NaN.
// This avoids typical CompareAndSwap loops from blocking forever, e.g.,
//
// for {
// old := atom.Load()
// new = f(old)
// if atom.CompareAndSwap(old, new) {
// break
// }
// }
//
// If CompareAndSwap did not match NaN to match, then the above would loop forever.
func (f *Float64) CompareAndSwap(old, new float64) (swapped bool) {
return f.v.CompareAndSwap(math.Float64bits(old), math.Float64bits(new))
} }
// String encodes the wrapped value as a string. // String encodes the wrapped value as a string.

1
vendor/go.uber.org/atomic/gen.go generated vendored
View File

@@ -24,3 +24,4 @@ package atomic
//go:generate bin/gen-atomicint -name=Int64 -wrapped=int64 -file=int64.go //go:generate bin/gen-atomicint -name=Int64 -wrapped=int64 -file=int64.go
//go:generate bin/gen-atomicint -name=Uint32 -wrapped=uint32 -unsigned -file=uint32.go //go:generate bin/gen-atomicint -name=Uint32 -wrapped=uint32 -unsigned -file=uint32.go
//go:generate bin/gen-atomicint -name=Uint64 -wrapped=uint64 -unsigned -file=uint64.go //go:generate bin/gen-atomicint -name=Uint64 -wrapped=uint64 -unsigned -file=uint64.go
//go:generate bin/gen-atomicint -name=Uintptr -wrapped=uintptr -unsigned -file=uintptr.go

31
vendor/go.uber.org/atomic/int32.go generated vendored
View File

@@ -1,6 +1,6 @@
// @generated Code generated by gen-atomicint. // @generated Code generated by gen-atomicint.
// Copyright (c) 2020 Uber Technologies, Inc. // Copyright (c) 2020-2023 Uber Technologies, Inc.
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -36,8 +36,8 @@ type Int32 struct {
} }
// NewInt32 creates a new Int32. // NewInt32 creates a new Int32.
func NewInt32(i int32) *Int32 { func NewInt32(val int32) *Int32 {
return &Int32{v: i} return &Int32{v: val}
} }
// Load atomically loads the wrapped value. // Load atomically loads the wrapped value.
@@ -46,13 +46,13 @@ func (i *Int32) Load() int32 {
} }
// Add atomically adds to the wrapped int32 and returns the new value. // Add atomically adds to the wrapped int32 and returns the new value.
func (i *Int32) Add(n int32) int32 { func (i *Int32) Add(delta int32) int32 {
return atomic.AddInt32(&i.v, n) return atomic.AddInt32(&i.v, delta)
} }
// Sub atomically subtracts from the wrapped int32 and returns the new value. // Sub atomically subtracts from the wrapped int32 and returns the new value.
func (i *Int32) Sub(n int32) int32 { func (i *Int32) Sub(delta int32) int32 {
return atomic.AddInt32(&i.v, -n) return atomic.AddInt32(&i.v, -delta)
} }
// Inc atomically increments the wrapped int32 and returns the new value. // Inc atomically increments the wrapped int32 and returns the new value.
@@ -66,18 +66,25 @@ func (i *Int32) Dec() int32 {
} }
// CAS is an atomic compare-and-swap. // CAS is an atomic compare-and-swap.
func (i *Int32) CAS(old, new int32) bool { //
// Deprecated: Use CompareAndSwap.
func (i *Int32) CAS(old, new int32) (swapped bool) {
return i.CompareAndSwap(old, new)
}
// CompareAndSwap is an atomic compare-and-swap.
func (i *Int32) CompareAndSwap(old, new int32) (swapped bool) {
return atomic.CompareAndSwapInt32(&i.v, old, new) return atomic.CompareAndSwapInt32(&i.v, old, new)
} }
// Store atomically stores the passed value. // Store atomically stores the passed value.
func (i *Int32) Store(n int32) { func (i *Int32) Store(val int32) {
atomic.StoreInt32(&i.v, n) atomic.StoreInt32(&i.v, val)
} }
// Swap atomically swaps the wrapped int32 and returns the old value. // Swap atomically swaps the wrapped int32 and returns the old value.
func (i *Int32) Swap(n int32) int32 { func (i *Int32) Swap(val int32) (old int32) {
return atomic.SwapInt32(&i.v, n) return atomic.SwapInt32(&i.v, val)
} }
// MarshalJSON encodes the wrapped int32 into JSON. // MarshalJSON encodes the wrapped int32 into JSON.

31
vendor/go.uber.org/atomic/int64.go generated vendored
View File

@@ -1,6 +1,6 @@
// @generated Code generated by gen-atomicint. // @generated Code generated by gen-atomicint.
// Copyright (c) 2020 Uber Technologies, Inc. // Copyright (c) 2020-2023 Uber Technologies, Inc.
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -36,8 +36,8 @@ type Int64 struct {
} }
// NewInt64 creates a new Int64. // NewInt64 creates a new Int64.
func NewInt64(i int64) *Int64 { func NewInt64(val int64) *Int64 {
return &Int64{v: i} return &Int64{v: val}
} }
// Load atomically loads the wrapped value. // Load atomically loads the wrapped value.
@@ -46,13 +46,13 @@ func (i *Int64) Load() int64 {
} }
// Add atomically adds to the wrapped int64 and returns the new value. // Add atomically adds to the wrapped int64 and returns the new value.
func (i *Int64) Add(n int64) int64 { func (i *Int64) Add(delta int64) int64 {
return atomic.AddInt64(&i.v, n) return atomic.AddInt64(&i.v, delta)
} }
// Sub atomically subtracts from the wrapped int64 and returns the new value. // Sub atomically subtracts from the wrapped int64 and returns the new value.
func (i *Int64) Sub(n int64) int64 { func (i *Int64) Sub(delta int64) int64 {
return atomic.AddInt64(&i.v, -n) return atomic.AddInt64(&i.v, -delta)
} }
// Inc atomically increments the wrapped int64 and returns the new value. // Inc atomically increments the wrapped int64 and returns the new value.
@@ -66,18 +66,25 @@ func (i *Int64) Dec() int64 {
} }
// CAS is an atomic compare-and-swap. // CAS is an atomic compare-and-swap.
func (i *Int64) CAS(old, new int64) bool { //
// Deprecated: Use CompareAndSwap.
func (i *Int64) CAS(old, new int64) (swapped bool) {
return i.CompareAndSwap(old, new)
}
// CompareAndSwap is an atomic compare-and-swap.
func (i *Int64) CompareAndSwap(old, new int64) (swapped bool) {
return atomic.CompareAndSwapInt64(&i.v, old, new) return atomic.CompareAndSwapInt64(&i.v, old, new)
} }
// Store atomically stores the passed value. // Store atomically stores the passed value.
func (i *Int64) Store(n int64) { func (i *Int64) Store(val int64) {
atomic.StoreInt64(&i.v, n) atomic.StoreInt64(&i.v, val)
} }
// Swap atomically swaps the wrapped int64 and returns the old value. // Swap atomically swaps the wrapped int64 and returns the old value.
func (i *Int64) Swap(n int64) int64 { func (i *Int64) Swap(val int64) (old int64) {
return atomic.SwapInt64(&i.v, n) return atomic.SwapInt64(&i.v, val)
} }
// MarshalJSON encodes the wrapped int64 into JSON. // MarshalJSON encodes the wrapped int64 into JSON.

31
vendor/go.uber.org/atomic/pointer_go118.go generated vendored Normal file
View File

@@ -0,0 +1,31 @@
// Copyright (c) 2022 Uber Technologies, Inc.
//
// 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.
//go:build go1.18
// +build go1.18
package atomic
import "fmt"
// String returns a human readable representation of a Pointer's underlying value.
func (p *Pointer[T]) String() string {
return fmt.Sprint(p.Load())
}

60
vendor/go.uber.org/atomic/pointer_go118_pre119.go generated vendored Normal file
View File

@@ -0,0 +1,60 @@
// Copyright (c) 2022 Uber Technologies, Inc.
//
// 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.
//go:build go1.18 && !go1.19
// +build go1.18,!go1.19
package atomic
import "unsafe"
type Pointer[T any] struct {
_ nocmp // disallow non-atomic comparison
p UnsafePointer
}
// NewPointer creates a new Pointer.
func NewPointer[T any](v *T) *Pointer[T] {
var p Pointer[T]
if v != nil {
p.p.Store(unsafe.Pointer(v))
}
return &p
}
// Load atomically loads the wrapped value.
func (p *Pointer[T]) Load() *T {
return (*T)(p.p.Load())
}
// Store atomically stores the passed value.
func (p *Pointer[T]) Store(val *T) {
p.p.Store(unsafe.Pointer(val))
}
// Swap atomically swaps the wrapped pointer and returns the old value.
func (p *Pointer[T]) Swap(val *T) (old *T) {
return (*T)(p.p.Swap(unsafe.Pointer(val)))
}
// CompareAndSwap is an atomic compare-and-swap.
func (p *Pointer[T]) CompareAndSwap(old, new *T) (swapped bool) {
return p.p.CompareAndSwap(unsafe.Pointer(old), unsafe.Pointer(new))
}

61
vendor/go.uber.org/atomic/pointer_go119.go generated vendored Normal file
View File

@@ -0,0 +1,61 @@
// Copyright (c) 2022 Uber Technologies, Inc.
//
// 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.
//go:build go1.19
// +build go1.19
package atomic
import "sync/atomic"
// Pointer is an atomic pointer of type *T.
type Pointer[T any] struct {
_ nocmp // disallow non-atomic comparison
p atomic.Pointer[T]
}
// NewPointer creates a new Pointer.
func NewPointer[T any](v *T) *Pointer[T] {
var p Pointer[T]
if v != nil {
p.p.Store(v)
}
return &p
}
// Load atomically loads the wrapped value.
func (p *Pointer[T]) Load() *T {
return p.p.Load()
}
// Store atomically stores the passed value.
func (p *Pointer[T]) Store(val *T) {
p.p.Store(val)
}
// Swap atomically swaps the wrapped pointer and returns the old value.
func (p *Pointer[T]) Swap(val *T) (old *T) {
return p.p.Swap(val)
}
// CompareAndSwap is an atomic compare-and-swap.
func (p *Pointer[T]) CompareAndSwap(old, new *T) (swapped bool) {
return p.p.CompareAndSwap(old, new)
}

38
vendor/go.uber.org/atomic/string.go generated vendored
View File

@@ -1,6 +1,6 @@
// @generated Code generated by gen-atomicwrapper. // @generated Code generated by gen-atomicwrapper.
// Copyright (c) 2020 Uber Technologies, Inc. // Copyright (c) 2020-2023 Uber Technologies, Inc.
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -32,23 +32,41 @@ type String struct {
var _zeroString string var _zeroString string
// NewString creates a new String. // NewString creates a new String.
func NewString(v string) *String { func NewString(val string) *String {
x := &String{} x := &String{}
if v != _zeroString { if val != _zeroString {
x.Store(v) x.Store(val)
} }
return x return x
} }
// Load atomically loads the wrapped string. // Load atomically loads the wrapped string.
func (x *String) Load() string { func (x *String) Load() string {
if v := x.v.Load(); v != nil { return unpackString(x.v.Load())
return v.(string)
}
return _zeroString
} }
// Store atomically stores the passed string. // Store atomically stores the passed string.
func (x *String) Store(v string) { func (x *String) Store(val string) {
x.v.Store(v) x.v.Store(packString(val))
}
// CompareAndSwap is an atomic compare-and-swap for string values.
func (x *String) CompareAndSwap(old, new string) (swapped bool) {
if x.v.CompareAndSwap(packString(old), packString(new)) {
return true
}
if old == _zeroString {
// If the old value is the empty value, then it's possible the
// underlying Value hasn't been set and is nil, so retry with nil.
return x.v.CompareAndSwap(nil, packString(new))
}
return false
}
// Swap atomically stores the given string and returns the old
// value.
func (x *String) Swap(val string) (old string) {
return unpackString(x.v.Swap(packString(val)))
} }

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2020 Uber Technologies, Inc. // Copyright (c) 2020-2023 Uber Technologies, Inc.
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -20,7 +20,18 @@
package atomic package atomic
//go:generate bin/gen-atomicwrapper -name=String -type=string -wrapped=Value -file=string.go //go:generate bin/gen-atomicwrapper -name=String -type=string -wrapped Value -pack packString -unpack unpackString -compareandswap -swap -file=string.go
func packString(s string) interface{} {
return s
}
func unpackString(v interface{}) string {
if s, ok := v.(string); ok {
return s
}
return ""
}
// String returns the wrapped value. // String returns the wrapped value.
func (s *String) String() string { func (s *String) String() string {

55
vendor/go.uber.org/atomic/time.go generated vendored Normal file
View File

@@ -0,0 +1,55 @@
// @generated Code generated by gen-atomicwrapper.
// Copyright (c) 2020-2023 Uber Technologies, Inc.
//
// 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.
package atomic
import (
"time"
)
// Time is an atomic type-safe wrapper for time.Time values.
type Time struct {
_ nocmp // disallow non-atomic comparison
v Value
}
var _zeroTime time.Time
// NewTime creates a new Time.
func NewTime(val time.Time) *Time {
x := &Time{}
if val != _zeroTime {
x.Store(val)
}
return x
}
// Load atomically loads the wrapped time.Time.
func (x *Time) Load() time.Time {
return unpackTime(x.v.Load())
}
// Store atomically stores the passed time.Time.
func (x *Time) Store(val time.Time) {
x.v.Store(packTime(val))
}

36
vendor/go.uber.org/atomic/time_ext.go generated vendored Normal file
View File

@@ -0,0 +1,36 @@
// Copyright (c) 2021 Uber Technologies, Inc.
//
// 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.
package atomic
import "time"
//go:generate bin/gen-atomicwrapper -name=Time -type=time.Time -wrapped=Value -pack=packTime -unpack=unpackTime -imports time -file=time.go
func packTime(t time.Time) interface{} {
return t
}
func unpackTime(v interface{}) time.Time {
if t, ok := v.(time.Time); ok {
return t
}
return time.Time{}
}

31
vendor/go.uber.org/atomic/uint32.go generated vendored
View File

@@ -1,6 +1,6 @@
// @generated Code generated by gen-atomicint. // @generated Code generated by gen-atomicint.
// Copyright (c) 2020 Uber Technologies, Inc. // Copyright (c) 2020-2023 Uber Technologies, Inc.
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -36,8 +36,8 @@ type Uint32 struct {
} }
// NewUint32 creates a new Uint32. // NewUint32 creates a new Uint32.
func NewUint32(i uint32) *Uint32 { func NewUint32(val uint32) *Uint32 {
return &Uint32{v: i} return &Uint32{v: val}
} }
// Load atomically loads the wrapped value. // Load atomically loads the wrapped value.
@@ -46,13 +46,13 @@ func (i *Uint32) Load() uint32 {
} }
// Add atomically adds to the wrapped uint32 and returns the new value. // Add atomically adds to the wrapped uint32 and returns the new value.
func (i *Uint32) Add(n uint32) uint32 { func (i *Uint32) Add(delta uint32) uint32 {
return atomic.AddUint32(&i.v, n) return atomic.AddUint32(&i.v, delta)
} }
// Sub atomically subtracts from the wrapped uint32 and returns the new value. // Sub atomically subtracts from the wrapped uint32 and returns the new value.
func (i *Uint32) Sub(n uint32) uint32 { func (i *Uint32) Sub(delta uint32) uint32 {
return atomic.AddUint32(&i.v, ^(n - 1)) return atomic.AddUint32(&i.v, ^(delta - 1))
} }
// Inc atomically increments the wrapped uint32 and returns the new value. // Inc atomically increments the wrapped uint32 and returns the new value.
@@ -66,18 +66,25 @@ func (i *Uint32) Dec() uint32 {
} }
// CAS is an atomic compare-and-swap. // CAS is an atomic compare-and-swap.
func (i *Uint32) CAS(old, new uint32) bool { //
// Deprecated: Use CompareAndSwap.
func (i *Uint32) CAS(old, new uint32) (swapped bool) {
return i.CompareAndSwap(old, new)
}
// CompareAndSwap is an atomic compare-and-swap.
func (i *Uint32) CompareAndSwap(old, new uint32) (swapped bool) {
return atomic.CompareAndSwapUint32(&i.v, old, new) return atomic.CompareAndSwapUint32(&i.v, old, new)
} }
// Store atomically stores the passed value. // Store atomically stores the passed value.
func (i *Uint32) Store(n uint32) { func (i *Uint32) Store(val uint32) {
atomic.StoreUint32(&i.v, n) atomic.StoreUint32(&i.v, val)
} }
// Swap atomically swaps the wrapped uint32 and returns the old value. // Swap atomically swaps the wrapped uint32 and returns the old value.
func (i *Uint32) Swap(n uint32) uint32 { func (i *Uint32) Swap(val uint32) (old uint32) {
return atomic.SwapUint32(&i.v, n) return atomic.SwapUint32(&i.v, val)
} }
// MarshalJSON encodes the wrapped uint32 into JSON. // MarshalJSON encodes the wrapped uint32 into JSON.

31
vendor/go.uber.org/atomic/uint64.go generated vendored
View File

@@ -1,6 +1,6 @@
// @generated Code generated by gen-atomicint. // @generated Code generated by gen-atomicint.
// Copyright (c) 2020 Uber Technologies, Inc. // Copyright (c) 2020-2023 Uber Technologies, Inc.
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@@ -36,8 +36,8 @@ type Uint64 struct {
} }
// NewUint64 creates a new Uint64. // NewUint64 creates a new Uint64.
func NewUint64(i uint64) *Uint64 { func NewUint64(val uint64) *Uint64 {
return &Uint64{v: i} return &Uint64{v: val}
} }
// Load atomically loads the wrapped value. // Load atomically loads the wrapped value.
@@ -46,13 +46,13 @@ func (i *Uint64) Load() uint64 {
} }
// Add atomically adds to the wrapped uint64 and returns the new value. // Add atomically adds to the wrapped uint64 and returns the new value.
func (i *Uint64) Add(n uint64) uint64 { func (i *Uint64) Add(delta uint64) uint64 {
return atomic.AddUint64(&i.v, n) return atomic.AddUint64(&i.v, delta)
} }
// Sub atomically subtracts from the wrapped uint64 and returns the new value. // Sub atomically subtracts from the wrapped uint64 and returns the new value.
func (i *Uint64) Sub(n uint64) uint64 { func (i *Uint64) Sub(delta uint64) uint64 {
return atomic.AddUint64(&i.v, ^(n - 1)) return atomic.AddUint64(&i.v, ^(delta - 1))
} }
// Inc atomically increments the wrapped uint64 and returns the new value. // Inc atomically increments the wrapped uint64 and returns the new value.
@@ -66,18 +66,25 @@ func (i *Uint64) Dec() uint64 {
} }
// CAS is an atomic compare-and-swap. // CAS is an atomic compare-and-swap.
func (i *Uint64) CAS(old, new uint64) bool { //
// Deprecated: Use CompareAndSwap.
func (i *Uint64) CAS(old, new uint64) (swapped bool) {
return i.CompareAndSwap(old, new)
}
// CompareAndSwap is an atomic compare-and-swap.
func (i *Uint64) CompareAndSwap(old, new uint64) (swapped bool) {
return atomic.CompareAndSwapUint64(&i.v, old, new) return atomic.CompareAndSwapUint64(&i.v, old, new)
} }
// Store atomically stores the passed value. // Store atomically stores the passed value.
func (i *Uint64) Store(n uint64) { func (i *Uint64) Store(val uint64) {
atomic.StoreUint64(&i.v, n) atomic.StoreUint64(&i.v, val)
} }
// Swap atomically swaps the wrapped uint64 and returns the old value. // Swap atomically swaps the wrapped uint64 and returns the old value.
func (i *Uint64) Swap(n uint64) uint64 { func (i *Uint64) Swap(val uint64) (old uint64) {
return atomic.SwapUint64(&i.v, n) return atomic.SwapUint64(&i.v, val)
} }
// MarshalJSON encodes the wrapped uint64 into JSON. // MarshalJSON encodes the wrapped uint64 into JSON.

109
vendor/go.uber.org/atomic/uintptr.go generated vendored Normal file
View File

@@ -0,0 +1,109 @@
// @generated Code generated by gen-atomicint.
// Copyright (c) 2020-2023 Uber Technologies, Inc.
//
// 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.
package atomic
import (
"encoding/json"
"strconv"
"sync/atomic"
)
// Uintptr is an atomic wrapper around uintptr.
type Uintptr struct {
_ nocmp // disallow non-atomic comparison
v uintptr
}
// NewUintptr creates a new Uintptr.
func NewUintptr(val uintptr) *Uintptr {
return &Uintptr{v: val}
}
// Load atomically loads the wrapped value.
func (i *Uintptr) Load() uintptr {
return atomic.LoadUintptr(&i.v)
}
// Add atomically adds to the wrapped uintptr and returns the new value.
func (i *Uintptr) Add(delta uintptr) uintptr {
return atomic.AddUintptr(&i.v, delta)
}
// Sub atomically subtracts from the wrapped uintptr and returns the new value.
func (i *Uintptr) Sub(delta uintptr) uintptr {
return atomic.AddUintptr(&i.v, ^(delta - 1))
}
// Inc atomically increments the wrapped uintptr and returns the new value.
func (i *Uintptr) Inc() uintptr {
return i.Add(1)
}
// Dec atomically decrements the wrapped uintptr and returns the new value.
func (i *Uintptr) Dec() uintptr {
return i.Sub(1)
}
// CAS is an atomic compare-and-swap.
//
// Deprecated: Use CompareAndSwap.
func (i *Uintptr) CAS(old, new uintptr) (swapped bool) {
return i.CompareAndSwap(old, new)
}
// CompareAndSwap is an atomic compare-and-swap.
func (i *Uintptr) CompareAndSwap(old, new uintptr) (swapped bool) {
return atomic.CompareAndSwapUintptr(&i.v, old, new)
}
// Store atomically stores the passed value.
func (i *Uintptr) Store(val uintptr) {
atomic.StoreUintptr(&i.v, val)
}
// Swap atomically swaps the wrapped uintptr and returns the old value.
func (i *Uintptr) Swap(val uintptr) (old uintptr) {
return atomic.SwapUintptr(&i.v, val)
}
// MarshalJSON encodes the wrapped uintptr into JSON.
func (i *Uintptr) MarshalJSON() ([]byte, error) {
return json.Marshal(i.Load())
}
// UnmarshalJSON decodes JSON into the wrapped uintptr.
func (i *Uintptr) UnmarshalJSON(b []byte) error {
var v uintptr
if err := json.Unmarshal(b, &v); err != nil {
return err
}
i.Store(v)
return nil
}
// String encodes the wrapped value as a string.
func (i *Uintptr) String() string {
v := i.Load()
return strconv.FormatUint(uint64(v), 10)
}

65
vendor/go.uber.org/atomic/unsafe_pointer.go generated vendored Normal file
View File

@@ -0,0 +1,65 @@
// Copyright (c) 2021-2022 Uber Technologies, Inc.
//
// 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.
package atomic
import (
"sync/atomic"
"unsafe"
)
// UnsafePointer is an atomic wrapper around unsafe.Pointer.
type UnsafePointer struct {
_ nocmp // disallow non-atomic comparison
v unsafe.Pointer
}
// NewUnsafePointer creates a new UnsafePointer.
func NewUnsafePointer(val unsafe.Pointer) *UnsafePointer {
return &UnsafePointer{v: val}
}
// Load atomically loads the wrapped value.
func (p *UnsafePointer) Load() unsafe.Pointer {
return atomic.LoadPointer(&p.v)
}
// Store atomically stores the passed value.
func (p *UnsafePointer) Store(val unsafe.Pointer) {
atomic.StorePointer(&p.v, val)
}
// Swap atomically swaps the wrapped unsafe.Pointer and returns the old value.
func (p *UnsafePointer) Swap(val unsafe.Pointer) (old unsafe.Pointer) {
return atomic.SwapPointer(&p.v, val)
}
// CAS is an atomic compare-and-swap.
//
// Deprecated: Use CompareAndSwap
func (p *UnsafePointer) CAS(old, new unsafe.Pointer) (swapped bool) {
return p.CompareAndSwap(old, new)
}
// CompareAndSwap is an atomic compare-and-swap.
func (p *UnsafePointer) CompareAndSwap(old, new unsafe.Pointer) (swapped bool) {
return atomic.CompareAndSwapPointer(&p.v, old, new)
}

4
vendor/go.uber.org/atomic/value.go generated vendored
View File

@@ -25,7 +25,7 @@ import "sync/atomic"
// Value shadows the type of the same name from sync/atomic // Value shadows the type of the same name from sync/atomic
// https://godoc.org/sync/atomic#Value // https://godoc.org/sync/atomic#Value
type Value struct { type Value struct {
atomic.Value
_ nocmp // disallow non-atomic comparison _ nocmp // disallow non-atomic comparison
atomic.Value
} }

27
vendor/golang.org/x/exp/LICENSE generated vendored
View File

@@ -1,27 +0,0 @@
Copyright (c) 2009 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.

22
vendor/golang.org/x/exp/PATENTS generated vendored
View File

@@ -1,22 +0,0 @@
Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
Google as part of the Go project.
Google hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section)
patent license to make, have made, use, offer to sell, sell, import,
transfer and otherwise run, modify and propagate the contents of this
implementation of Go, where such license applies only to those patent
claims, both currently owned or controlled by Google and acquired in
the future, licensable by Google that are necessarily infringed by this
implementation of Go. This grant does not include claims that would be
infringed only as a consequence of further modification of this
implementation. If you or your agent or exclusive licensee institute or
order or agree to the institution of patent litigation against any
entity (including a cross-claim or counterclaim in a lawsuit) alleging
that this implementation of Go or any code incorporated within this
implementation of Go constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any patent
rights granted to you under this License for this implementation of Go
shall terminate as of the date such litigation is filed.

View File

@@ -1,50 +0,0 @@
// Copyright 2021 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 constraints defines a set of useful constraints to be used
// with type parameters.
package constraints
// Signed is a constraint that permits any signed integer type.
// If future releases of Go add new predeclared signed integer types,
// this constraint will be modified to include them.
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
// Unsigned is a constraint that permits any unsigned integer type.
// If future releases of Go add new predeclared unsigned integer types,
// this constraint will be modified to include them.
type Unsigned interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
// Integer is a constraint that permits any integer type.
// If future releases of Go add new predeclared integer types,
// this constraint will be modified to include them.
type Integer interface {
Signed | Unsigned
}
// Float is a constraint that permits any floating-point type.
// If future releases of Go add new predeclared floating-point types,
// this constraint will be modified to include them.
type Float interface {
~float32 | ~float64
}
// Complex is a constraint that permits any complex numeric type.
// If future releases of Go add new predeclared complex numeric types,
// this constraint will be modified to include them.
type Complex interface {
~complex64 | ~complex128
}
// Ordered is a constraint that permits any ordered type: any type
// that supports the operators < <= >= >.
// If future releases of Go add new ordered types,
// this constraint will be modified to include them.
type Ordered interface {
Integer | Float | ~string
}

94
vendor/golang.org/x/exp/maps/maps.go generated vendored
View File

@@ -1,94 +0,0 @@
// Copyright 2021 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 maps defines various functions useful with maps of any type.
package maps
// Keys returns the keys of the map m.
// The keys will be in an indeterminate order.
func Keys[M ~map[K]V, K comparable, V any](m M) []K {
r := make([]K, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}
// Values returns the values of the map m.
// The values will be in an indeterminate order.
func Values[M ~map[K]V, K comparable, V any](m M) []V {
r := make([]V, 0, len(m))
for _, v := range m {
r = append(r, v)
}
return r
}
// Equal reports whether two maps contain the same key/value pairs.
// Values are compared using ==.
func Equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool {
if len(m1) != len(m2) {
return false
}
for k, v1 := range m1 {
if v2, ok := m2[k]; !ok || v1 != v2 {
return false
}
}
return true
}
// EqualFunc is like Equal, but compares values using eq.
// Keys are still compared with ==.
func EqualFunc[M1 ~map[K]V1, M2 ~map[K]V2, K comparable, V1, V2 any](m1 M1, m2 M2, eq func(V1, V2) bool) bool {
if len(m1) != len(m2) {
return false
}
for k, v1 := range m1 {
if v2, ok := m2[k]; !ok || !eq(v1, v2) {
return false
}
}
return true
}
// Clear removes all entries from m, leaving it empty.
func Clear[M ~map[K]V, K comparable, V any](m M) {
for k := range m {
delete(m, k)
}
}
// Clone returns a copy of m. This is a shallow clone:
// the new keys and values are set using ordinary assignment.
func Clone[M ~map[K]V, K comparable, V any](m M) M {
// Preserve nil in case it matters.
if m == nil {
return nil
}
r := make(M, len(m))
for k, v := range m {
r[k] = v
}
return r
}
// Copy copies all key/value pairs in src adding them to dst.
// When a key in src is already present in dst,
// the value in dst will be overwritten by the value associated
// with the key in src.
func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2) {
for k, v := range src {
dst[k] = v
}
}
// DeleteFunc deletes any key/value pairs from m for which del returns true.
func DeleteFunc[M ~map[K]V, K comparable, V any](m M, del func(K, V) bool) {
for k, v := range m {
if del(k, v) {
delete(m, k)
}
}
}

View File

@@ -1,44 +0,0 @@
// Copyright 2023 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 slices
import "golang.org/x/exp/constraints"
// min is a version of the predeclared function from the Go 1.21 release.
func min[T constraints.Ordered](a, b T) T {
if a < b || isNaN(a) {
return a
}
return b
}
// max is a version of the predeclared function from the Go 1.21 release.
func max[T constraints.Ordered](a, b T) T {
if a > b || isNaN(a) {
return a
}
return b
}
// cmpLess is a copy of cmp.Less from the Go 1.21 release.
func cmpLess[T constraints.Ordered](x, y T) bool {
return (isNaN(x) && !isNaN(y)) || x < y
}
// cmpCompare is a copy of cmp.Compare from the Go 1.21 release.
func cmpCompare[T constraints.Ordered](x, y T) int {
xNaN := isNaN(x)
yNaN := isNaN(y)
if xNaN && yNaN {
return 0
}
if xNaN || x < y {
return -1
}
if yNaN || x > y {
return +1
}
return 0
}

View File

@@ -1,515 +0,0 @@
// Copyright 2021 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 slices defines various functions useful with slices of any type.
package slices
import (
"unsafe"
"golang.org/x/exp/constraints"
)
// Equal reports whether two slices are equal: the same length and all
// elements equal. If the lengths are different, Equal returns false.
// Otherwise, the elements are compared in increasing index order, and the
// comparison stops at the first unequal pair.
// Floating point NaNs are not considered equal.
func Equal[S ~[]E, E comparable](s1, s2 S) bool {
if len(s1) != len(s2) {
return false
}
for i := range s1 {
if s1[i] != s2[i] {
return false
}
}
return true
}
// EqualFunc reports whether two slices are equal using an equality
// function on each pair of elements. If the lengths are different,
// EqualFunc returns false. Otherwise, the elements are compared in
// increasing index order, and the comparison stops at the first index
// for which eq returns false.
func EqualFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, eq func(E1, E2) bool) bool {
if len(s1) != len(s2) {
return false
}
for i, v1 := range s1 {
v2 := s2[i]
if !eq(v1, v2) {
return false
}
}
return true
}
// Compare compares the elements of s1 and s2, using [cmp.Compare] on each pair
// of elements. The elements are compared sequentially, starting at index 0,
// until one element is not equal to the other.
// The result of comparing the first non-matching elements is returned.
// If both slices are equal until one of them ends, the shorter slice is
// considered less than the longer one.
// The result is 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2.
func Compare[S ~[]E, E constraints.Ordered](s1, s2 S) int {
for i, v1 := range s1 {
if i >= len(s2) {
return +1
}
v2 := s2[i]
if c := cmpCompare(v1, v2); c != 0 {
return c
}
}
if len(s1) < len(s2) {
return -1
}
return 0
}
// CompareFunc is like [Compare] but uses a custom comparison function on each
// pair of elements.
// The result is the first non-zero result of cmp; if cmp always
// returns 0 the result is 0 if len(s1) == len(s2), -1 if len(s1) < len(s2),
// and +1 if len(s1) > len(s2).
func CompareFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, cmp func(E1, E2) int) int {
for i, v1 := range s1 {
if i >= len(s2) {
return +1
}
v2 := s2[i]
if c := cmp(v1, v2); c != 0 {
return c
}
}
if len(s1) < len(s2) {
return -1
}
return 0
}
// Index returns the index of the first occurrence of v in s,
// or -1 if not present.
func Index[S ~[]E, E comparable](s S, v E) int {
for i := range s {
if v == s[i] {
return i
}
}
return -1
}
// IndexFunc returns the first index i satisfying f(s[i]),
// or -1 if none do.
func IndexFunc[S ~[]E, E any](s S, f func(E) bool) int {
for i := range s {
if f(s[i]) {
return i
}
}
return -1
}
// Contains reports whether v is present in s.
func Contains[S ~[]E, E comparable](s S, v E) bool {
return Index(s, v) >= 0
}
// ContainsFunc reports whether at least one
// element e of s satisfies f(e).
func ContainsFunc[S ~[]E, E any](s S, f func(E) bool) bool {
return IndexFunc(s, f) >= 0
}
// Insert inserts the values v... into s at index i,
// returning the modified slice.
// The elements at s[i:] are shifted up to make room.
// In the returned slice r, r[i] == v[0],
// and r[i+len(v)] == value originally at r[i].
// Insert panics if i is out of range.
// This function is O(len(s) + len(v)).
func Insert[S ~[]E, E any](s S, i int, v ...E) S {
m := len(v)
if m == 0 {
return s
}
n := len(s)
if i == n {
return append(s, v...)
}
if n+m > cap(s) {
// Use append rather than make so that we bump the size of
// the slice up to the next storage class.
// This is what Grow does but we don't call Grow because
// that might copy the values twice.
s2 := append(s[:i], make(S, n+m-i)...)
copy(s2[i:], v)
copy(s2[i+m:], s[i:])
return s2
}
s = s[:n+m]
// before:
// s: aaaaaaaabbbbccccccccdddd
// ^ ^ ^ ^
// i i+m n n+m
// after:
// s: aaaaaaaavvvvbbbbcccccccc
// ^ ^ ^ ^
// i i+m n n+m
//
// a are the values that don't move in s.
// v are the values copied in from v.
// b and c are the values from s that are shifted up in index.
// d are the values that get overwritten, never to be seen again.
if !overlaps(v, s[i+m:]) {
// Easy case - v does not overlap either the c or d regions.
// (It might be in some of a or b, or elsewhere entirely.)
// The data we copy up doesn't write to v at all, so just do it.
copy(s[i+m:], s[i:])
// Now we have
// s: aaaaaaaabbbbbbbbcccccccc
// ^ ^ ^ ^
// i i+m n n+m
// Note the b values are duplicated.
copy(s[i:], v)
// Now we have
// s: aaaaaaaavvvvbbbbcccccccc
// ^ ^ ^ ^
// i i+m n n+m
// That's the result we want.
return s
}
// The hard case - v overlaps c or d. We can't just shift up
// the data because we'd move or clobber the values we're trying
// to insert.
// So instead, write v on top of d, then rotate.
copy(s[n:], v)
// Now we have
// s: aaaaaaaabbbbccccccccvvvv
// ^ ^ ^ ^
// i i+m n n+m
rotateRight(s[i:], m)
// Now we have
// s: aaaaaaaavvvvbbbbcccccccc
// ^ ^ ^ ^
// i i+m n n+m
// That's the result we want.
return s
}
// clearSlice sets all elements up to the length of s to the zero value of E.
// We may use the builtin clear func instead, and remove clearSlice, when upgrading
// to Go 1.21+.
func clearSlice[S ~[]E, E any](s S) {
var zero E
for i := range s {
s[i] = zero
}
}
// Delete removes the elements s[i:j] from s, returning the modified slice.
// Delete panics if j > len(s) or s[i:j] is not a valid slice of s.
// Delete is O(len(s)-i), so if many items must be deleted, it is better to
// make a single call deleting them all together than to delete one at a time.
// Delete zeroes the elements s[len(s)-(j-i):len(s)].
func Delete[S ~[]E, E any](s S, i, j int) S {
_ = s[i:j:len(s)] // bounds check
if i == j {
return s
}
oldlen := len(s)
s = append(s[:i], s[j:]...)
clearSlice(s[len(s):oldlen]) // zero/nil out the obsolete elements, for GC
return s
}
// DeleteFunc removes any elements from s for which del returns true,
// returning the modified slice.
// DeleteFunc zeroes the elements between the new length and the original length.
func DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S {
i := IndexFunc(s, del)
if i == -1 {
return s
}
// Don't start copying elements until we find one to delete.
for j := i + 1; j < len(s); j++ {
if v := s[j]; !del(v) {
s[i] = v
i++
}
}
clearSlice(s[i:]) // zero/nil out the obsolete elements, for GC
return s[:i]
}
// Replace replaces the elements s[i:j] by the given v, and returns the
// modified slice. Replace panics if s[i:j] is not a valid slice of s.
// When len(v) < (j-i), Replace zeroes the elements between the new length and the original length.
func Replace[S ~[]E, E any](s S, i, j int, v ...E) S {
_ = s[i:j] // verify that i:j is a valid subslice
if i == j {
return Insert(s, i, v...)
}
if j == len(s) {
return append(s[:i], v...)
}
tot := len(s[:i]) + len(v) + len(s[j:])
if tot > cap(s) {
// Too big to fit, allocate and copy over.
s2 := append(s[:i], make(S, tot-i)...) // See Insert
copy(s2[i:], v)
copy(s2[i+len(v):], s[j:])
return s2
}
r := s[:tot]
if i+len(v) <= j {
// Easy, as v fits in the deleted portion.
copy(r[i:], v)
if i+len(v) != j {
copy(r[i+len(v):], s[j:])
}
clearSlice(s[tot:]) // zero/nil out the obsolete elements, for GC
return r
}
// We are expanding (v is bigger than j-i).
// The situation is something like this:
// (example has i=4,j=8,len(s)=16,len(v)=6)
// s: aaaaxxxxbbbbbbbbyy
// ^ ^ ^ ^
// i j len(s) tot
// a: prefix of s
// x: deleted range
// b: more of s
// y: area to expand into
if !overlaps(r[i+len(v):], v) {
// Easy, as v is not clobbered by the first copy.
copy(r[i+len(v):], s[j:])
copy(r[i:], v)
return r
}
// This is a situation where we don't have a single place to which
// we can copy v. Parts of it need to go to two different places.
// We want to copy the prefix of v into y and the suffix into x, then
// rotate |y| spots to the right.
//
// v[2:] v[:2]
// | |
// s: aaaavvvvbbbbbbbbvv
// ^ ^ ^ ^
// i j len(s) tot
//
// If either of those two destinations don't alias v, then we're good.
y := len(v) - (j - i) // length of y portion
if !overlaps(r[i:j], v) {
copy(r[i:j], v[y:])
copy(r[len(s):], v[:y])
rotateRight(r[i:], y)
return r
}
if !overlaps(r[len(s):], v) {
copy(r[len(s):], v[:y])
copy(r[i:j], v[y:])
rotateRight(r[i:], y)
return r
}
// Now we know that v overlaps both x and y.
// That means that the entirety of b is *inside* v.
// So we don't need to preserve b at all; instead we
// can copy v first, then copy the b part of v out of
// v to the right destination.
k := startIdx(v, s[j:])
copy(r[i:], v)
copy(r[i+len(v):], r[i+k:])
return r
}
// Clone returns a copy of the slice.
// The elements are copied using assignment, so this is a shallow clone.
func Clone[S ~[]E, E any](s S) S {
// Preserve nil in case it matters.
if s == nil {
return nil
}
return append(S([]E{}), s...)
}
// Compact replaces consecutive runs of equal elements with a single copy.
// This is like the uniq command found on Unix.
// Compact modifies the contents of the slice s and returns the modified slice,
// which may have a smaller length.
// Compact zeroes the elements between the new length and the original length.
func Compact[S ~[]E, E comparable](s S) S {
if len(s) < 2 {
return s
}
i := 1
for k := 1; k < len(s); k++ {
if s[k] != s[k-1] {
if i != k {
s[i] = s[k]
}
i++
}
}
clearSlice(s[i:]) // zero/nil out the obsolete elements, for GC
return s[:i]
}
// CompactFunc is like [Compact] but uses an equality function to compare elements.
// For runs of elements that compare equal, CompactFunc keeps the first one.
// CompactFunc zeroes the elements between the new length and the original length.
func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S {
if len(s) < 2 {
return s
}
i := 1
for k := 1; k < len(s); k++ {
if !eq(s[k], s[k-1]) {
if i != k {
s[i] = s[k]
}
i++
}
}
clearSlice(s[i:]) // zero/nil out the obsolete elements, for GC
return s[:i]
}
// Grow increases the slice's capacity, if necessary, to guarantee space for
// another n elements. After Grow(n), at least n elements can be appended
// to the slice without another allocation. If n is negative or too large to
// allocate the memory, Grow panics.
func Grow[S ~[]E, E any](s S, n int) S {
if n < 0 {
panic("cannot be negative")
}
if n -= cap(s) - len(s); n > 0 {
// TODO(https://go.dev/issue/53888): Make using []E instead of S
// to workaround a compiler bug where the runtime.growslice optimization
// does not take effect. Revert when the compiler is fixed.
s = append([]E(s)[:cap(s)], make([]E, n)...)[:len(s)]
}
return s
}
// Clip removes unused capacity from the slice, returning s[:len(s):len(s)].
func Clip[S ~[]E, E any](s S) S {
return s[:len(s):len(s)]
}
// Rotation algorithm explanation:
//
// rotate left by 2
// start with
// 0123456789
// split up like this
// 01 234567 89
// swap first 2 and last 2
// 89 234567 01
// join first parts
// 89234567 01
// recursively rotate first left part by 2
// 23456789 01
// join at the end
// 2345678901
//
// rotate left by 8
// start with
// 0123456789
// split up like this
// 01 234567 89
// swap first 2 and last 2
// 89 234567 01
// join last parts
// 89 23456701
// recursively rotate second part left by 6
// 89 01234567
// join at the end
// 8901234567
// TODO: There are other rotate algorithms.
// This algorithm has the desirable property that it moves each element exactly twice.
// The triple-reverse algorithm is simpler and more cache friendly, but takes more writes.
// The follow-cycles algorithm can be 1-write but it is not very cache friendly.
// rotateLeft rotates b left by n spaces.
// s_final[i] = s_orig[i+r], wrapping around.
func rotateLeft[E any](s []E, r int) {
for r != 0 && r != len(s) {
if r*2 <= len(s) {
swap(s[:r], s[len(s)-r:])
s = s[:len(s)-r]
} else {
swap(s[:len(s)-r], s[r:])
s, r = s[len(s)-r:], r*2-len(s)
}
}
}
func rotateRight[E any](s []E, r int) {
rotateLeft(s, len(s)-r)
}
// swap swaps the contents of x and y. x and y must be equal length and disjoint.
func swap[E any](x, y []E) {
for i := 0; i < len(x); i++ {
x[i], y[i] = y[i], x[i]
}
}
// overlaps reports whether the memory ranges a[0:len(a)] and b[0:len(b)] overlap.
func overlaps[E any](a, b []E) bool {
if len(a) == 0 || len(b) == 0 {
return false
}
elemSize := unsafe.Sizeof(a[0])
if elemSize == 0 {
return false
}
// TODO: use a runtime/unsafe facility once one becomes available. See issue 12445.
// Also see crypto/internal/alias/alias.go:AnyOverlap
return uintptr(unsafe.Pointer(&a[0])) <= uintptr(unsafe.Pointer(&b[len(b)-1]))+(elemSize-1) &&
uintptr(unsafe.Pointer(&b[0])) <= uintptr(unsafe.Pointer(&a[len(a)-1]))+(elemSize-1)
}
// startIdx returns the index in haystack where the needle starts.
// prerequisite: the needle must be aliased entirely inside the haystack.
func startIdx[E any](haystack, needle []E) int {
p := &needle[0]
for i := range haystack {
if p == &haystack[i] {
return i
}
}
// TODO: what if the overlap is by a non-integral number of Es?
panic("needle not found")
}
// Reverse reverses the elements of the slice in place.
func Reverse[S ~[]E, E any](s S) {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
}

View File

@@ -1,197 +0,0 @@
// Copyright 2022 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.
//go:generate go run $GOROOT/src/sort/gen_sort_variants.go -exp
package slices
import (
"math/bits"
"golang.org/x/exp/constraints"
)
// Sort sorts a slice of any ordered type in ascending order.
// When sorting floating-point numbers, NaNs are ordered before other values.
func Sort[S ~[]E, E constraints.Ordered](x S) {
n := len(x)
pdqsortOrdered(x, 0, n, bits.Len(uint(n)))
}
// SortFunc sorts the slice x in ascending order as determined by the cmp
// function. This sort is not guaranteed to be stable.
// cmp(a, b) should return a negative number when a < b, a positive number when
// a > b and zero when a == b or when a is not comparable to b in the sense
// of the formal definition of Strict Weak Ordering.
//
// SortFunc requires that cmp is a strict weak ordering.
// See https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings.
// To indicate 'uncomparable', return 0 from the function.
func SortFunc[S ~[]E, E any](x S, cmp func(a, b E) int) {
n := len(x)
pdqsortCmpFunc(x, 0, n, bits.Len(uint(n)), cmp)
}
// SortStableFunc sorts the slice x while keeping the original order of equal
// elements, using cmp to compare elements in the same way as [SortFunc].
func SortStableFunc[S ~[]E, E any](x S, cmp func(a, b E) int) {
stableCmpFunc(x, len(x), cmp)
}
// IsSorted reports whether x is sorted in ascending order.
func IsSorted[S ~[]E, E constraints.Ordered](x S) bool {
for i := len(x) - 1; i > 0; i-- {
if cmpLess(x[i], x[i-1]) {
return false
}
}
return true
}
// IsSortedFunc reports whether x is sorted in ascending order, with cmp as the
// comparison function as defined by [SortFunc].
func IsSortedFunc[S ~[]E, E any](x S, cmp func(a, b E) int) bool {
for i := len(x) - 1; i > 0; i-- {
if cmp(x[i], x[i-1]) < 0 {
return false
}
}
return true
}
// Min returns the minimal value in x. It panics if x is empty.
// For floating-point numbers, Min propagates NaNs (any NaN value in x
// forces the output to be NaN).
func Min[S ~[]E, E constraints.Ordered](x S) E {
if len(x) < 1 {
panic("slices.Min: empty list")
}
m := x[0]
for i := 1; i < len(x); i++ {
m = min(m, x[i])
}
return m
}
// MinFunc returns the minimal value in x, using cmp to compare elements.
// It panics if x is empty. If there is more than one minimal element
// according to the cmp function, MinFunc returns the first one.
func MinFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E {
if len(x) < 1 {
panic("slices.MinFunc: empty list")
}
m := x[0]
for i := 1; i < len(x); i++ {
if cmp(x[i], m) < 0 {
m = x[i]
}
}
return m
}
// Max returns the maximal value in x. It panics if x is empty.
// For floating-point E, Max propagates NaNs (any NaN value in x
// forces the output to be NaN).
func Max[S ~[]E, E constraints.Ordered](x S) E {
if len(x) < 1 {
panic("slices.Max: empty list")
}
m := x[0]
for i := 1; i < len(x); i++ {
m = max(m, x[i])
}
return m
}
// MaxFunc returns the maximal value in x, using cmp to compare elements.
// It panics if x is empty. If there is more than one maximal element
// according to the cmp function, MaxFunc returns the first one.
func MaxFunc[S ~[]E, E any](x S, cmp func(a, b E) int) E {
if len(x) < 1 {
panic("slices.MaxFunc: empty list")
}
m := x[0]
for i := 1; i < len(x); i++ {
if cmp(x[i], m) > 0 {
m = x[i]
}
}
return m
}
// BinarySearch searches for target in a sorted slice and returns the position
// where target is found, or the position where target would appear in the
// sort order; it also returns a bool saying whether the target is really found
// in the slice. The slice must be sorted in increasing order.
func BinarySearch[S ~[]E, E constraints.Ordered](x S, target E) (int, bool) {
// Inlining is faster than calling BinarySearchFunc with a lambda.
n := len(x)
// Define x[-1] < target and x[n] >= target.
// Invariant: x[i-1] < target, x[j] >= target.
i, j := 0, n
for i < j {
h := int(uint(i+j) >> 1) // avoid overflow when computing h
// i ≤ h < j
if cmpLess(x[h], target) {
i = h + 1 // preserves x[i-1] < target
} else {
j = h // preserves x[j] >= target
}
}
// i == j, x[i-1] < target, and x[j] (= x[i]) >= target => answer is i.
return i, i < n && (x[i] == target || (isNaN(x[i]) && isNaN(target)))
}
// BinarySearchFunc works like [BinarySearch], but uses a custom comparison
// function. The slice must be sorted in increasing order, where "increasing"
// is defined by cmp. cmp should return 0 if the slice element matches
// the target, a negative number if the slice element precedes the target,
// or a positive number if the slice element follows the target.
// cmp must implement the same ordering as the slice, such that if
// cmp(a, t) < 0 and cmp(b, t) >= 0, then a must precede b in the slice.
func BinarySearchFunc[S ~[]E, E, T any](x S, target T, cmp func(E, T) int) (int, bool) {
n := len(x)
// Define cmp(x[-1], target) < 0 and cmp(x[n], target) >= 0 .
// Invariant: cmp(x[i - 1], target) < 0, cmp(x[j], target) >= 0.
i, j := 0, n
for i < j {
h := int(uint(i+j) >> 1) // avoid overflow when computing h
// i ≤ h < j
if cmp(x[h], target) < 0 {
i = h + 1 // preserves cmp(x[i - 1], target) < 0
} else {
j = h // preserves cmp(x[j], target) >= 0
}
}
// i == j, cmp(x[i-1], target) < 0, and cmp(x[j], target) (= cmp(x[i], target)) >= 0 => answer is i.
return i, i < n && cmp(x[i], target) == 0
}
type sortedHint int // hint for pdqsort when choosing the pivot
const (
unknownHint sortedHint = iota
increasingHint
decreasingHint
)
// xorshift paper: https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf
type xorshift uint64
func (r *xorshift) Next() uint64 {
*r ^= *r << 13
*r ^= *r >> 17
*r ^= *r << 5
return uint64(*r)
}
func nextPowerOfTwo(length int) uint {
return 1 << bits.Len(uint(length))
}
// isNaN reports whether x is a NaN without requiring the math package.
// This will always return false if T is not floating-point.
func isNaN[T constraints.Ordered](x T) bool {
return x != x
}

View File

@@ -1,479 +0,0 @@
// Code generated by gen_sort_variants.go; DO NOT EDIT.
// Copyright 2022 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 slices
// insertionSortCmpFunc sorts data[a:b] using insertion sort.
func insertionSortCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) {
for i := a + 1; i < b; i++ {
for j := i; j > a && (cmp(data[j], data[j-1]) < 0); j-- {
data[j], data[j-1] = data[j-1], data[j]
}
}
}
// siftDownCmpFunc implements the heap property on data[lo:hi].
// first is an offset into the array where the root of the heap lies.
func siftDownCmpFunc[E any](data []E, lo, hi, first int, cmp func(a, b E) int) {
root := lo
for {
child := 2*root + 1
if child >= hi {
break
}
if child+1 < hi && (cmp(data[first+child], data[first+child+1]) < 0) {
child++
}
if !(cmp(data[first+root], data[first+child]) < 0) {
return
}
data[first+root], data[first+child] = data[first+child], data[first+root]
root = child
}
}
func heapSortCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) {
first := a
lo := 0
hi := b - a
// Build heap with greatest element at top.
for i := (hi - 1) / 2; i >= 0; i-- {
siftDownCmpFunc(data, i, hi, first, cmp)
}
// Pop elements, largest first, into end of data.
for i := hi - 1; i >= 0; i-- {
data[first], data[first+i] = data[first+i], data[first]
siftDownCmpFunc(data, lo, i, first, cmp)
}
}
// pdqsortCmpFunc sorts data[a:b].
// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort.
// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf
// C++ implementation: https://github.com/orlp/pdqsort
// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/
// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort.
func pdqsortCmpFunc[E any](data []E, a, b, limit int, cmp func(a, b E) int) {
const maxInsertion = 12
var (
wasBalanced = true // whether the last partitioning was reasonably balanced
wasPartitioned = true // whether the slice was already partitioned
)
for {
length := b - a
if length <= maxInsertion {
insertionSortCmpFunc(data, a, b, cmp)
return
}
// Fall back to heapsort if too many bad choices were made.
if limit == 0 {
heapSortCmpFunc(data, a, b, cmp)
return
}
// If the last partitioning was imbalanced, we need to breaking patterns.
if !wasBalanced {
breakPatternsCmpFunc(data, a, b, cmp)
limit--
}
pivot, hint := choosePivotCmpFunc(data, a, b, cmp)
if hint == decreasingHint {
reverseRangeCmpFunc(data, a, b, cmp)
// The chosen pivot was pivot-a elements after the start of the array.
// After reversing it is pivot-a elements before the end of the array.
// The idea came from Rust's implementation.
pivot = (b - 1) - (pivot - a)
hint = increasingHint
}
// The slice is likely already sorted.
if wasBalanced && wasPartitioned && hint == increasingHint {
if partialInsertionSortCmpFunc(data, a, b, cmp) {
return
}
}
// Probably the slice contains many duplicate elements, partition the slice into
// elements equal to and elements greater than the pivot.
if a > 0 && !(cmp(data[a-1], data[pivot]) < 0) {
mid := partitionEqualCmpFunc(data, a, b, pivot, cmp)
a = mid
continue
}
mid, alreadyPartitioned := partitionCmpFunc(data, a, b, pivot, cmp)
wasPartitioned = alreadyPartitioned
leftLen, rightLen := mid-a, b-mid
balanceThreshold := length / 8
if leftLen < rightLen {
wasBalanced = leftLen >= balanceThreshold
pdqsortCmpFunc(data, a, mid, limit, cmp)
a = mid + 1
} else {
wasBalanced = rightLen >= balanceThreshold
pdqsortCmpFunc(data, mid+1, b, limit, cmp)
b = mid
}
}
}
// partitionCmpFunc does one quicksort partition.
// Let p = data[pivot]
// Moves elements in data[a:b] around, so that data[i]<p and data[j]>=p for i<newpivot and j>newpivot.
// On return, data[newpivot] = p
func partitionCmpFunc[E any](data []E, a, b, pivot int, cmp func(a, b E) int) (newpivot int, alreadyPartitioned bool) {
data[a], data[pivot] = data[pivot], data[a]
i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned
for i <= j && (cmp(data[i], data[a]) < 0) {
i++
}
for i <= j && !(cmp(data[j], data[a]) < 0) {
j--
}
if i > j {
data[j], data[a] = data[a], data[j]
return j, true
}
data[i], data[j] = data[j], data[i]
i++
j--
for {
for i <= j && (cmp(data[i], data[a]) < 0) {
i++
}
for i <= j && !(cmp(data[j], data[a]) < 0) {
j--
}
if i > j {
break
}
data[i], data[j] = data[j], data[i]
i++
j--
}
data[j], data[a] = data[a], data[j]
return j, false
}
// partitionEqualCmpFunc partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot].
// It assumed that data[a:b] does not contain elements smaller than the data[pivot].
func partitionEqualCmpFunc[E any](data []E, a, b, pivot int, cmp func(a, b E) int) (newpivot int) {
data[a], data[pivot] = data[pivot], data[a]
i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned
for {
for i <= j && !(cmp(data[a], data[i]) < 0) {
i++
}
for i <= j && (cmp(data[a], data[j]) < 0) {
j--
}
if i > j {
break
}
data[i], data[j] = data[j], data[i]
i++
j--
}
return i
}
// partialInsertionSortCmpFunc partially sorts a slice, returns true if the slice is sorted at the end.
func partialInsertionSortCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) bool {
const (
maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted
shortestShifting = 50 // don't shift any elements on short arrays
)
i := a + 1
for j := 0; j < maxSteps; j++ {
for i < b && !(cmp(data[i], data[i-1]) < 0) {
i++
}
if i == b {
return true
}
if b-a < shortestShifting {
return false
}
data[i], data[i-1] = data[i-1], data[i]
// Shift the smaller one to the left.
if i-a >= 2 {
for j := i - 1; j >= 1; j-- {
if !(cmp(data[j], data[j-1]) < 0) {
break
}
data[j], data[j-1] = data[j-1], data[j]
}
}
// Shift the greater one to the right.
if b-i >= 2 {
for j := i + 1; j < b; j++ {
if !(cmp(data[j], data[j-1]) < 0) {
break
}
data[j], data[j-1] = data[j-1], data[j]
}
}
}
return false
}
// breakPatternsCmpFunc scatters some elements around in an attempt to break some patterns
// that might cause imbalanced partitions in quicksort.
func breakPatternsCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) {
length := b - a
if length >= 8 {
random := xorshift(length)
modulus := nextPowerOfTwo(length)
for idx := a + (length/4)*2 - 1; idx <= a+(length/4)*2+1; idx++ {
other := int(uint(random.Next()) & (modulus - 1))
if other >= length {
other -= length
}
data[idx], data[a+other] = data[a+other], data[idx]
}
}
}
// choosePivotCmpFunc chooses a pivot in data[a:b].
//
// [0,8): chooses a static pivot.
// [8,shortestNinther): uses the simple median-of-three method.
// [shortestNinther,∞): uses the Tukey ninther method.
func choosePivotCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) (pivot int, hint sortedHint) {
const (
shortestNinther = 50
maxSwaps = 4 * 3
)
l := b - a
var (
swaps int
i = a + l/4*1
j = a + l/4*2
k = a + l/4*3
)
if l >= 8 {
if l >= shortestNinther {
// Tukey ninther method, the idea came from Rust's implementation.
i = medianAdjacentCmpFunc(data, i, &swaps, cmp)
j = medianAdjacentCmpFunc(data, j, &swaps, cmp)
k = medianAdjacentCmpFunc(data, k, &swaps, cmp)
}
// Find the median among i, j, k and stores it into j.
j = medianCmpFunc(data, i, j, k, &swaps, cmp)
}
switch swaps {
case 0:
return j, increasingHint
case maxSwaps:
return j, decreasingHint
default:
return j, unknownHint
}
}
// order2CmpFunc returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a.
func order2CmpFunc[E any](data []E, a, b int, swaps *int, cmp func(a, b E) int) (int, int) {
if cmp(data[b], data[a]) < 0 {
*swaps++
return b, a
}
return a, b
}
// medianCmpFunc returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c.
func medianCmpFunc[E any](data []E, a, b, c int, swaps *int, cmp func(a, b E) int) int {
a, b = order2CmpFunc(data, a, b, swaps, cmp)
b, c = order2CmpFunc(data, b, c, swaps, cmp)
a, b = order2CmpFunc(data, a, b, swaps, cmp)
return b
}
// medianAdjacentCmpFunc finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a.
func medianAdjacentCmpFunc[E any](data []E, a int, swaps *int, cmp func(a, b E) int) int {
return medianCmpFunc(data, a-1, a, a+1, swaps, cmp)
}
func reverseRangeCmpFunc[E any](data []E, a, b int, cmp func(a, b E) int) {
i := a
j := b - 1
for i < j {
data[i], data[j] = data[j], data[i]
i++
j--
}
}
func swapRangeCmpFunc[E any](data []E, a, b, n int, cmp func(a, b E) int) {
for i := 0; i < n; i++ {
data[a+i], data[b+i] = data[b+i], data[a+i]
}
}
func stableCmpFunc[E any](data []E, n int, cmp func(a, b E) int) {
blockSize := 20 // must be > 0
a, b := 0, blockSize
for b <= n {
insertionSortCmpFunc(data, a, b, cmp)
a = b
b += blockSize
}
insertionSortCmpFunc(data, a, n, cmp)
for blockSize < n {
a, b = 0, 2*blockSize
for b <= n {
symMergeCmpFunc(data, a, a+blockSize, b, cmp)
a = b
b += 2 * blockSize
}
if m := a + blockSize; m < n {
symMergeCmpFunc(data, a, m, n, cmp)
}
blockSize *= 2
}
}
// symMergeCmpFunc merges the two sorted subsequences data[a:m] and data[m:b] using
// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum
// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz
// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in
// Computer Science, pages 714-723. Springer, 2004.
//
// Let M = m-a and N = b-n. Wolog M < N.
// The recursion depth is bound by ceil(log(N+M)).
// The algorithm needs O(M*log(N/M + 1)) calls to data.Less.
// The algorithm needs O((M+N)*log(M)) calls to data.Swap.
//
// The paper gives O((M+N)*log(M)) as the number of assignments assuming a
// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation
// in the paper carries through for Swap operations, especially as the block
// swapping rotate uses only O(M+N) Swaps.
//
// symMerge assumes non-degenerate arguments: a < m && m < b.
// Having the caller check this condition eliminates many leaf recursion calls,
// which improves performance.
func symMergeCmpFunc[E any](data []E, a, m, b int, cmp func(a, b E) int) {
// Avoid unnecessary recursions of symMerge
// by direct insertion of data[a] into data[m:b]
// if data[a:m] only contains one element.
if m-a == 1 {
// Use binary search to find the lowest index i
// such that data[i] >= data[a] for m <= i < b.
// Exit the search loop with i == b in case no such index exists.
i := m
j := b
for i < j {
h := int(uint(i+j) >> 1)
if cmp(data[h], data[a]) < 0 {
i = h + 1
} else {
j = h
}
}
// Swap values until data[a] reaches the position before i.
for k := a; k < i-1; k++ {
data[k], data[k+1] = data[k+1], data[k]
}
return
}
// Avoid unnecessary recursions of symMerge
// by direct insertion of data[m] into data[a:m]
// if data[m:b] only contains one element.
if b-m == 1 {
// Use binary search to find the lowest index i
// such that data[i] > data[m] for a <= i < m.
// Exit the search loop with i == m in case no such index exists.
i := a
j := m
for i < j {
h := int(uint(i+j) >> 1)
if !(cmp(data[m], data[h]) < 0) {
i = h + 1
} else {
j = h
}
}
// Swap values until data[m] reaches the position i.
for k := m; k > i; k-- {
data[k], data[k-1] = data[k-1], data[k]
}
return
}
mid := int(uint(a+b) >> 1)
n := mid + m
var start, r int
if m > mid {
start = n - b
r = mid
} else {
start = a
r = m
}
p := n - 1
for start < r {
c := int(uint(start+r) >> 1)
if !(cmp(data[p-c], data[c]) < 0) {
start = c + 1
} else {
r = c
}
}
end := n - start
if start < m && m < end {
rotateCmpFunc(data, start, m, end, cmp)
}
if a < start && start < mid {
symMergeCmpFunc(data, a, start, mid, cmp)
}
if mid < end && end < b {
symMergeCmpFunc(data, mid, end, b, cmp)
}
}
// rotateCmpFunc rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data:
// Data of the form 'x u v y' is changed to 'x v u y'.
// rotate performs at most b-a many calls to data.Swap,
// and it assumes non-degenerate arguments: a < m && m < b.
func rotateCmpFunc[E any](data []E, a, m, b int, cmp func(a, b E) int) {
i := m - a
j := b - m
for i != j {
if i > j {
swapRangeCmpFunc(data, m-i, m, j, cmp)
i -= j
} else {
swapRangeCmpFunc(data, m-i, m+j-i, i, cmp)
j -= i
}
}
// i == j
swapRangeCmpFunc(data, m-i, m, i, cmp)
}

View File

@@ -1,481 +0,0 @@
// Code generated by gen_sort_variants.go; DO NOT EDIT.
// Copyright 2022 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 slices
import "golang.org/x/exp/constraints"
// insertionSortOrdered sorts data[a:b] using insertion sort.
func insertionSortOrdered[E constraints.Ordered](data []E, a, b int) {
for i := a + 1; i < b; i++ {
for j := i; j > a && cmpLess(data[j], data[j-1]); j-- {
data[j], data[j-1] = data[j-1], data[j]
}
}
}
// siftDownOrdered implements the heap property on data[lo:hi].
// first is an offset into the array where the root of the heap lies.
func siftDownOrdered[E constraints.Ordered](data []E, lo, hi, first int) {
root := lo
for {
child := 2*root + 1
if child >= hi {
break
}
if child+1 < hi && cmpLess(data[first+child], data[first+child+1]) {
child++
}
if !cmpLess(data[first+root], data[first+child]) {
return
}
data[first+root], data[first+child] = data[first+child], data[first+root]
root = child
}
}
func heapSortOrdered[E constraints.Ordered](data []E, a, b int) {
first := a
lo := 0
hi := b - a
// Build heap with greatest element at top.
for i := (hi - 1) / 2; i >= 0; i-- {
siftDownOrdered(data, i, hi, first)
}
// Pop elements, largest first, into end of data.
for i := hi - 1; i >= 0; i-- {
data[first], data[first+i] = data[first+i], data[first]
siftDownOrdered(data, lo, i, first)
}
}
// pdqsortOrdered sorts data[a:b].
// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort.
// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf
// C++ implementation: https://github.com/orlp/pdqsort
// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/
// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort.
func pdqsortOrdered[E constraints.Ordered](data []E, a, b, limit int) {
const maxInsertion = 12
var (
wasBalanced = true // whether the last partitioning was reasonably balanced
wasPartitioned = true // whether the slice was already partitioned
)
for {
length := b - a
if length <= maxInsertion {
insertionSortOrdered(data, a, b)
return
}
// Fall back to heapsort if too many bad choices were made.
if limit == 0 {
heapSortOrdered(data, a, b)
return
}
// If the last partitioning was imbalanced, we need to breaking patterns.
if !wasBalanced {
breakPatternsOrdered(data, a, b)
limit--
}
pivot, hint := choosePivotOrdered(data, a, b)
if hint == decreasingHint {
reverseRangeOrdered(data, a, b)
// The chosen pivot was pivot-a elements after the start of the array.
// After reversing it is pivot-a elements before the end of the array.
// The idea came from Rust's implementation.
pivot = (b - 1) - (pivot - a)
hint = increasingHint
}
// The slice is likely already sorted.
if wasBalanced && wasPartitioned && hint == increasingHint {
if partialInsertionSortOrdered(data, a, b) {
return
}
}
// Probably the slice contains many duplicate elements, partition the slice into
// elements equal to and elements greater than the pivot.
if a > 0 && !cmpLess(data[a-1], data[pivot]) {
mid := partitionEqualOrdered(data, a, b, pivot)
a = mid
continue
}
mid, alreadyPartitioned := partitionOrdered(data, a, b, pivot)
wasPartitioned = alreadyPartitioned
leftLen, rightLen := mid-a, b-mid
balanceThreshold := length / 8
if leftLen < rightLen {
wasBalanced = leftLen >= balanceThreshold
pdqsortOrdered(data, a, mid, limit)
a = mid + 1
} else {
wasBalanced = rightLen >= balanceThreshold
pdqsortOrdered(data, mid+1, b, limit)
b = mid
}
}
}
// partitionOrdered does one quicksort partition.
// Let p = data[pivot]
// Moves elements in data[a:b] around, so that data[i]<p and data[j]>=p for i<newpivot and j>newpivot.
// On return, data[newpivot] = p
func partitionOrdered[E constraints.Ordered](data []E, a, b, pivot int) (newpivot int, alreadyPartitioned bool) {
data[a], data[pivot] = data[pivot], data[a]
i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned
for i <= j && cmpLess(data[i], data[a]) {
i++
}
for i <= j && !cmpLess(data[j], data[a]) {
j--
}
if i > j {
data[j], data[a] = data[a], data[j]
return j, true
}
data[i], data[j] = data[j], data[i]
i++
j--
for {
for i <= j && cmpLess(data[i], data[a]) {
i++
}
for i <= j && !cmpLess(data[j], data[a]) {
j--
}
if i > j {
break
}
data[i], data[j] = data[j], data[i]
i++
j--
}
data[j], data[a] = data[a], data[j]
return j, false
}
// partitionEqualOrdered partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot].
// It assumed that data[a:b] does not contain elements smaller than the data[pivot].
func partitionEqualOrdered[E constraints.Ordered](data []E, a, b, pivot int) (newpivot int) {
data[a], data[pivot] = data[pivot], data[a]
i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned
for {
for i <= j && !cmpLess(data[a], data[i]) {
i++
}
for i <= j && cmpLess(data[a], data[j]) {
j--
}
if i > j {
break
}
data[i], data[j] = data[j], data[i]
i++
j--
}
return i
}
// partialInsertionSortOrdered partially sorts a slice, returns true if the slice is sorted at the end.
func partialInsertionSortOrdered[E constraints.Ordered](data []E, a, b int) bool {
const (
maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted
shortestShifting = 50 // don't shift any elements on short arrays
)
i := a + 1
for j := 0; j < maxSteps; j++ {
for i < b && !cmpLess(data[i], data[i-1]) {
i++
}
if i == b {
return true
}
if b-a < shortestShifting {
return false
}
data[i], data[i-1] = data[i-1], data[i]
// Shift the smaller one to the left.
if i-a >= 2 {
for j := i - 1; j >= 1; j-- {
if !cmpLess(data[j], data[j-1]) {
break
}
data[j], data[j-1] = data[j-1], data[j]
}
}
// Shift the greater one to the right.
if b-i >= 2 {
for j := i + 1; j < b; j++ {
if !cmpLess(data[j], data[j-1]) {
break
}
data[j], data[j-1] = data[j-1], data[j]
}
}
}
return false
}
// breakPatternsOrdered scatters some elements around in an attempt to break some patterns
// that might cause imbalanced partitions in quicksort.
func breakPatternsOrdered[E constraints.Ordered](data []E, a, b int) {
length := b - a
if length >= 8 {
random := xorshift(length)
modulus := nextPowerOfTwo(length)
for idx := a + (length/4)*2 - 1; idx <= a+(length/4)*2+1; idx++ {
other := int(uint(random.Next()) & (modulus - 1))
if other >= length {
other -= length
}
data[idx], data[a+other] = data[a+other], data[idx]
}
}
}
// choosePivotOrdered chooses a pivot in data[a:b].
//
// [0,8): chooses a static pivot.
// [8,shortestNinther): uses the simple median-of-three method.
// [shortestNinther,∞): uses the Tukey ninther method.
func choosePivotOrdered[E constraints.Ordered](data []E, a, b int) (pivot int, hint sortedHint) {
const (
shortestNinther = 50
maxSwaps = 4 * 3
)
l := b - a
var (
swaps int
i = a + l/4*1
j = a + l/4*2
k = a + l/4*3
)
if l >= 8 {
if l >= shortestNinther {
// Tukey ninther method, the idea came from Rust's implementation.
i = medianAdjacentOrdered(data, i, &swaps)
j = medianAdjacentOrdered(data, j, &swaps)
k = medianAdjacentOrdered(data, k, &swaps)
}
// Find the median among i, j, k and stores it into j.
j = medianOrdered(data, i, j, k, &swaps)
}
switch swaps {
case 0:
return j, increasingHint
case maxSwaps:
return j, decreasingHint
default:
return j, unknownHint
}
}
// order2Ordered returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a.
func order2Ordered[E constraints.Ordered](data []E, a, b int, swaps *int) (int, int) {
if cmpLess(data[b], data[a]) {
*swaps++
return b, a
}
return a, b
}
// medianOrdered returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c.
func medianOrdered[E constraints.Ordered](data []E, a, b, c int, swaps *int) int {
a, b = order2Ordered(data, a, b, swaps)
b, c = order2Ordered(data, b, c, swaps)
a, b = order2Ordered(data, a, b, swaps)
return b
}
// medianAdjacentOrdered finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a.
func medianAdjacentOrdered[E constraints.Ordered](data []E, a int, swaps *int) int {
return medianOrdered(data, a-1, a, a+1, swaps)
}
func reverseRangeOrdered[E constraints.Ordered](data []E, a, b int) {
i := a
j := b - 1
for i < j {
data[i], data[j] = data[j], data[i]
i++
j--
}
}
func swapRangeOrdered[E constraints.Ordered](data []E, a, b, n int) {
for i := 0; i < n; i++ {
data[a+i], data[b+i] = data[b+i], data[a+i]
}
}
func stableOrdered[E constraints.Ordered](data []E, n int) {
blockSize := 20 // must be > 0
a, b := 0, blockSize
for b <= n {
insertionSortOrdered(data, a, b)
a = b
b += blockSize
}
insertionSortOrdered(data, a, n)
for blockSize < n {
a, b = 0, 2*blockSize
for b <= n {
symMergeOrdered(data, a, a+blockSize, b)
a = b
b += 2 * blockSize
}
if m := a + blockSize; m < n {
symMergeOrdered(data, a, m, n)
}
blockSize *= 2
}
}
// symMergeOrdered merges the two sorted subsequences data[a:m] and data[m:b] using
// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum
// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz
// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in
// Computer Science, pages 714-723. Springer, 2004.
//
// Let M = m-a and N = b-n. Wolog M < N.
// The recursion depth is bound by ceil(log(N+M)).
// The algorithm needs O(M*log(N/M + 1)) calls to data.Less.
// The algorithm needs O((M+N)*log(M)) calls to data.Swap.
//
// The paper gives O((M+N)*log(M)) as the number of assignments assuming a
// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation
// in the paper carries through for Swap operations, especially as the block
// swapping rotate uses only O(M+N) Swaps.
//
// symMerge assumes non-degenerate arguments: a < m && m < b.
// Having the caller check this condition eliminates many leaf recursion calls,
// which improves performance.
func symMergeOrdered[E constraints.Ordered](data []E, a, m, b int) {
// Avoid unnecessary recursions of symMerge
// by direct insertion of data[a] into data[m:b]
// if data[a:m] only contains one element.
if m-a == 1 {
// Use binary search to find the lowest index i
// such that data[i] >= data[a] for m <= i < b.
// Exit the search loop with i == b in case no such index exists.
i := m
j := b
for i < j {
h := int(uint(i+j) >> 1)
if cmpLess(data[h], data[a]) {
i = h + 1
} else {
j = h
}
}
// Swap values until data[a] reaches the position before i.
for k := a; k < i-1; k++ {
data[k], data[k+1] = data[k+1], data[k]
}
return
}
// Avoid unnecessary recursions of symMerge
// by direct insertion of data[m] into data[a:m]
// if data[m:b] only contains one element.
if b-m == 1 {
// Use binary search to find the lowest index i
// such that data[i] > data[m] for a <= i < m.
// Exit the search loop with i == m in case no such index exists.
i := a
j := m
for i < j {
h := int(uint(i+j) >> 1)
if !cmpLess(data[m], data[h]) {
i = h + 1
} else {
j = h
}
}
// Swap values until data[m] reaches the position i.
for k := m; k > i; k-- {
data[k], data[k-1] = data[k-1], data[k]
}
return
}
mid := int(uint(a+b) >> 1)
n := mid + m
var start, r int
if m > mid {
start = n - b
r = mid
} else {
start = a
r = m
}
p := n - 1
for start < r {
c := int(uint(start+r) >> 1)
if !cmpLess(data[p-c], data[c]) {
start = c + 1
} else {
r = c
}
}
end := n - start
if start < m && m < end {
rotateOrdered(data, start, m, end)
}
if a < start && start < mid {
symMergeOrdered(data, a, start, mid)
}
if mid < end && end < b {
symMergeOrdered(data, mid, end, b)
}
}
// rotateOrdered rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data:
// Data of the form 'x u v y' is changed to 'x v u y'.
// rotate performs at most b-a many calls to data.Swap,
// and it assumes non-degenerate arguments: a < m && m < b.
func rotateOrdered[E constraints.Ordered](data []E, a, m, b int) {
i := m - a
j := b - m
for i != j {
if i > j {
swapRangeOrdered(data, m-i, m, j)
i -= j
} else {
swapRangeOrdered(data, m-i, m+j-i, i)
j -= i
}
}
// i == j
swapRangeOrdered(data, m-i, m, i)
}

3
vendor/golang.org/x/sys/cpu/cpu.go generated vendored
View File

@@ -72,6 +72,9 @@ var X86 struct {
HasSSSE3 bool // Supplemental streaming SIMD extension 3 HasSSSE3 bool // Supplemental streaming SIMD extension 3
HasSSE41 bool // Streaming SIMD extension 4 and 4.1 HasSSE41 bool // Streaming SIMD extension 4 and 4.1
HasSSE42 bool // Streaming SIMD extension 4 and 4.2 HasSSE42 bool // Streaming SIMD extension 4 and 4.2
HasAVXIFMA bool // Advanced vector extension Integer Fused Multiply Add
HasAVXVNNI bool // Advanced vector extension Vector Neural Network Instructions
HasAVXVNNIInt8 bool // Advanced vector extension Vector Neural Network Int8 instructions
_ CacheLinePad _ CacheLinePad
} }

View File

@@ -53,6 +53,9 @@ func initOptions() {
{Name: "sse41", Feature: &X86.HasSSE41}, {Name: "sse41", Feature: &X86.HasSSE41},
{Name: "sse42", Feature: &X86.HasSSE42}, {Name: "sse42", Feature: &X86.HasSSE42},
{Name: "ssse3", Feature: &X86.HasSSSE3}, {Name: "ssse3", Feature: &X86.HasSSSE3},
{Name: "avxifma", Feature: &X86.HasAVXIFMA},
{Name: "avxvnni", Feature: &X86.HasAVXVNNI},
{Name: "avxvnniint8", Feature: &X86.HasAVXVNNIInt8},
// These capabilities should always be enabled on amd64: // These capabilities should always be enabled on amd64:
{Name: "sse2", Feature: &X86.HasSSE2, Required: runtime.GOARCH == "amd64"}, {Name: "sse2", Feature: &X86.HasSSE2, Required: runtime.GOARCH == "amd64"},
@@ -106,7 +109,7 @@ func archInit() {
return return
} }
_, ebx7, ecx7, edx7 := cpuid(7, 0) eax7, ebx7, ecx7, edx7 := cpuid(7, 0)
X86.HasBMI1 = isSet(3, ebx7) X86.HasBMI1 = isSet(3, ebx7)
X86.HasAVX2 = isSet(5, ebx7) && osSupportsAVX X86.HasAVX2 = isSet(5, ebx7) && osSupportsAVX
X86.HasBMI2 = isSet(8, ebx7) X86.HasBMI2 = isSet(8, ebx7)
@@ -134,14 +137,24 @@ func archInit() {
X86.HasAVX512VAES = isSet(9, ecx7) X86.HasAVX512VAES = isSet(9, ecx7)
X86.HasAVX512VBMI2 = isSet(6, ecx7) X86.HasAVX512VBMI2 = isSet(6, ecx7)
X86.HasAVX512BITALG = isSet(12, ecx7) X86.HasAVX512BITALG = isSet(12, ecx7)
eax71, _, _, _ := cpuid(7, 1)
X86.HasAVX512BF16 = isSet(5, eax71)
} }
X86.HasAMXTile = isSet(24, edx7) X86.HasAMXTile = isSet(24, edx7)
X86.HasAMXInt8 = isSet(25, edx7) X86.HasAMXInt8 = isSet(25, edx7)
X86.HasAMXBF16 = isSet(22, edx7) X86.HasAMXBF16 = isSet(22, edx7)
// These features depend on the second level of extended features.
if eax7 >= 1 {
eax71, _, _, edx71 := cpuid(7, 1)
if X86.HasAVX512 {
X86.HasAVX512BF16 = isSet(5, eax71)
}
if X86.HasAVX {
X86.HasAVXIFMA = isSet(23, eax71)
X86.HasAVXVNNI = isSet(4, eax71)
X86.HasAVXVNNIInt8 = isSet(4, edx71)
}
}
} }
func isSet(bitpos uint, value uint32) bool { func isSet(bitpos uint, value uint32) bool {

View File

@@ -1,6 +1,6 @@
.PHONY: benchmark .PHONY: benchmark
benchmark: benchmark:
go test -bench=. go test -bench=. ./...
.PHONY: cover .PHONY: cover
cover: cover:
@@ -13,4 +13,3 @@ lint:
.PHONY: test .PHONY: test
test: test:
go test -coverprofile=cover.out -shuffle on ./... go test -coverprofile=cover.out -shuffle on ./...

View File

@@ -71,6 +71,47 @@ func NavbarLink(href, name, currentPath string) Node {
(Some people don't like dot-imports, and luckily it's completely optional.) (Some people don't like dot-imports, and luckily it's completely optional.)
For a more complete example, see [the examples directory](internal/examples/). For a more complete example, see [the examples directory](internal/examples/).
There's also the [gomponents-starter-kit](https://github.com/maragudk/gomponents-starter-kit) for a full application template.
## Architecture
gomponents is organized into several packages:
- `gomponents`: Core interfaces and functions like `Node`, `El`, `Attr`, and helpers like `Map`, `Group`, `If`, `Text`, `Raw`.
- `gomponents/html`: HTML elements and attributes.
- `gomponents/components`: Higher-level components and utilities.
- `gomponents/http`: HTTP-related utilities for web servers.
### Void Elements
Void elements in HTML (like `<br>`, `<img>`, `<input>`) don't have closing tags.
gomponents handles these correctly by checking against an internal list of void elements during rendering.
When you create a void element, any child nodes that are not attributes will be ignored automatically to ensure valid HTML output.
## Performance Considerations
gomponents renders directly to an `io.Writer`, making it efficient for server-side rendering.
The library avoids unnecessary allocations where possible.
## FAQ
### Is gomponents production-ready?
Yes! gomponents is mature, stable, fully tested with 100% coverage, and has been used in production by myself and many others.
### Should I choose `html/template`, Templ, or gomponents?
These are all good choices, and it largely comes down to preference.
I wrote gomponents because I didn't like how I think it's hard to pass data around between templates in `html/template`.
gomponents is pure Go, with no extra build step like Templ, so it works with all tools that already support Go.
That said, both `html/template` and Templ will do the same thing as gomponents in the end. Try them all and choose what you like!
### I don't like how HTML looks in Go.
First of all, that's not a question. 😉
More seriously, think of gomponents like a DSL for HTML. You're building UI components. Give it a day, and it'll feel natural.
### What's up with the specially named elements and attributes? ### What's up with the specially named elements and attributes?

View File

@@ -1,3 +1,2 @@
ignore: ignore:
- "examples" - "internal/"
- "internal/assert"

View File

@@ -250,7 +250,7 @@ func Rawf(format string, a ...interface{}) Node {
// Map a slice of anything to a [Group] (which is just a slice of [Node]-s). // Map a slice of anything to a [Group] (which is just a slice of [Node]-s).
func Map[T any](ts []T, cb func(T) Node) Group { func Map[T any](ts []T, cb func(T) Node) Group {
var nodes []Node nodes := make([]Node, 0, len(ts))
for _, t := range ts { for _, t := range ts {
nodes = append(nodes, cb(t)) nodes = append(nodes, cb(t))
} }

View File

@@ -40,6 +40,10 @@ func Disabled() g.Node {
return g.Attr("disabled") return g.Attr("disabled")
} }
func Download(v string) g.Node {
return g.Attr("download", v)
}
func Draggable(v string) g.Node { func Draggable(v string) g.Node {
return g.Attr("draggable", v) return g.Attr("draggable", v)
} }
@@ -133,14 +137,38 @@ func DataAttr(name, v string) g.Node {
return Data(name, v) return Data(name, v)
} }
func SlotAttr(v string) g.Node {
return g.Attr("slot", v)
}
func For(v string) g.Node { func For(v string) g.Node {
return g.Attr("for", v) return g.Attr("for", v)
} }
func FormAction(v string) g.Node {
return g.Attr("formaction", v)
}
func FormAttr(v string) g.Node { func FormAttr(v string) g.Node {
return g.Attr("form", v) return g.Attr("form", v)
} }
func FormEncType(v string) g.Node {
return g.Attr("formenctype", v)
}
func FormMethod(v string) g.Node {
return g.Attr("formmethod", v)
}
func FormNoValidate() g.Node {
return g.Attr("formnovalidate")
}
func FormTarget(v string) g.Node {
return g.Attr("formtarget", v)
}
func Height(v string) g.Node { func Height(v string) g.Node {
return g.Attr("height", v) return g.Attr("height", v)
} }
@@ -209,6 +237,18 @@ func Placeholder(v string) g.Node {
return g.Attr("placeholder", v) return g.Attr("placeholder", v)
} }
func Popover(value ...string) g.Node {
return g.Attr("popover", value...)
}
func PopoverTarget(v string) g.Node {
return g.Attr("popovertarget", v)
}
func PopoverTargetAction(v string) g.Node {
return g.Attr("popovertargetaction", v)
}
func Poster(v string) g.Node { func Poster(v string) g.Node {
return g.Attr("poster", v) return g.Attr("poster", v)
} }
@@ -217,6 +257,10 @@ func Preload(v string) g.Node {
return g.Attr("preload", v) return g.Attr("preload", v)
} }
func ReferrerPolicy(v string) g.Node {
return g.Attr("referrerpolicy", v)
}
func Rel(v string) g.Node { func Rel(v string) g.Node {
return g.Attr("rel", v) return g.Attr("rel", v)
} }

View File

@@ -264,6 +264,10 @@ func Select(children ...g.Node) g.Node {
return g.El("select", children...) return g.El("select", children...)
} }
func SlotEl(children ...g.Node) g.Node {
return g.El("slot", children...)
}
func Source(children ...g.Node) g.Node { func Source(children ...g.Node) g.Node {
return g.El("source", children...) return g.El("source", children...)
} }
@@ -296,6 +300,10 @@ func Td(children ...g.Node) g.Node {
return g.El("td", children...) return g.El("td", children...)
} }
func Template(children ...g.Node) g.Node {
return g.El("template", children...)
}
func Textarea(children ...g.Node) g.Node { func Textarea(children ...g.Node) g.Node {
return g.El("textarea", children...) return g.El("textarea", children...)
} }

49
vendor/modules.txt vendored
View File

@@ -1,7 +1,13 @@
# github.com/PuerkitoBio/goquery v1.10.2
## explicit; go 1.23
# github.com/andybalholm/cascadia v1.3.3
## explicit; go 1.16
# github.com/aymerick/douceur v0.2.0 # github.com/aymerick/douceur v0.2.0
## explicit ## explicit
github.com/aymerick/douceur/css github.com/aymerick/douceur/css
github.com/aymerick/douceur/parser github.com/aymerick/douceur/parser
# github.com/btcsuite/btcd v0.20.1-beta
## explicit; go 1.12
# github.com/btcsuite/btcutil v1.0.2 # github.com/btcsuite/btcutil v1.0.2
## explicit; go 1.13 ## explicit; go 1.13
github.com/btcsuite/btcutil/base58 github.com/btcsuite/btcutil/base58
@@ -11,8 +17,8 @@ github.com/caarlos0/env/v11
# github.com/davecgh/go-spew v1.1.1 # github.com/davecgh/go-spew v1.1.1
## explicit ## explicit
github.com/davecgh/go-spew/spew github.com/davecgh/go-spew/spew
# github.com/go-co-op/gocron/v2 v2.15.0 # github.com/go-co-op/gocron/v2 v2.16.0
## explicit; go 1.20 ## explicit; go 1.21.0
github.com/go-co-op/gocron/v2 github.com/go-co-op/gocron/v2
# github.com/go-jet/jet/v2 v2.12.0 # github.com/go-jet/jet/v2 v2.12.0
## explicit; go 1.21 ## explicit; go 1.21
@@ -36,7 +42,7 @@ github.com/golang-migrate/migrate/v4/internal/url
github.com/golang-migrate/migrate/v4/source github.com/golang-migrate/migrate/v4/source
github.com/golang-migrate/migrate/v4/source/file github.com/golang-migrate/migrate/v4/source/file
github.com/golang-migrate/migrate/v4/source/iofs github.com/golang-migrate/migrate/v4/source/iofs
# github.com/gomarkdown/markdown v0.0.0-20241105142532-d03b89096d81 # github.com/gomarkdown/markdown v0.0.0-20250207164621-7a1f277a159e
## explicit; go 1.12 ## explicit; go 1.12
github.com/gomarkdown/markdown github.com/gomarkdown/markdown
github.com/gomarkdown/markdown/ast github.com/gomarkdown/markdown/ast
@@ -54,14 +60,20 @@ github.com/hashicorp/errwrap
# github.com/hashicorp/go-multierror v1.1.1 # github.com/hashicorp/go-multierror v1.1.1
## explicit; go 1.13 ## explicit; go 1.13
github.com/hashicorp/go-multierror github.com/hashicorp/go-multierror
# github.com/jmoiron/sqlx v1.4.0
## explicit; go 1.10
github.com/jmoiron/sqlx
github.com/jmoiron/sqlx/reflectx
# github.com/joho/godotenv v1.5.1 # github.com/joho/godotenv v1.5.1
## explicit; go 1.12 ## explicit; go 1.12
github.com/joho/godotenv github.com/joho/godotenv
# github.com/jonboulle/clockwork v0.4.0 # github.com/jonboulle/clockwork v0.5.0
## explicit; go 1.15 ## explicit; go 1.21
github.com/jonboulle/clockwork github.com/jonboulle/clockwork
# github.com/kr/pretty v0.3.1 # github.com/kr/pretty v0.3.1
## explicit; go 1.12 ## explicit; go 1.12
# github.com/kr/text v0.2.0
## explicit
# github.com/mattn/go-sqlite3 v1.14.24 # github.com/mattn/go-sqlite3 v1.14.24
## explicit; go 1.19 ## explicit; go 1.19
github.com/mattn/go-sqlite3 github.com/mattn/go-sqlite3
@@ -78,6 +90,8 @@ github.com/pmezard/go-difflib/difflib
# github.com/robfig/cron/v3 v3.0.1 # github.com/robfig/cron/v3 v3.0.1
## explicit; go 1.12 ## explicit; go 1.12
github.com/robfig/cron/v3 github.com/robfig/cron/v3
# github.com/rogpeppe/go-internal v1.12.0
## explicit; go 1.20
# github.com/sethvargo/go-diceware v0.5.0 # github.com/sethvargo/go-diceware v0.5.0
## explicit; go 1.22 ## explicit; go 1.22
github.com/sethvargo/go-diceware/diceware github.com/sethvargo/go-diceware/diceware
@@ -89,29 +103,26 @@ github.com/sethvargo/go-password/password
github.com/stretchr/testify/assert github.com/stretchr/testify/assert
github.com/stretchr/testify/assert/yaml github.com/stretchr/testify/assert/yaml
github.com/stretchr/testify/require github.com/stretchr/testify/require
# go.uber.org/atomic v1.7.0 # go.uber.org/atomic v1.11.0
## explicit; go 1.13 ## explicit; go 1.18
go.uber.org/atomic go.uber.org/atomic
# golang.org/x/crypto v0.31.0 # golang.org/x/crypto v0.36.0
## explicit; go 1.20 ## explicit; go 1.23.0
golang.org/x/crypto/bcrypt golang.org/x/crypto/bcrypt
golang.org/x/crypto/blowfish golang.org/x/crypto/blowfish
# golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 # golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
## explicit; go 1.20 ## explicit; go 1.23.0
golang.org/x/exp/constraints # golang.org/x/net v0.37.0
golang.org/x/exp/maps ## explicit; go 1.23.0
golang.org/x/exp/slices
# golang.org/x/net v0.33.0
## explicit; go 1.18
golang.org/x/net/html golang.org/x/net/html
golang.org/x/net/html/atom golang.org/x/net/html/atom
# golang.org/x/sys v0.28.0 # golang.org/x/sys v0.31.0
## explicit; go 1.18 ## explicit; go 1.23.0
golang.org/x/sys/cpu golang.org/x/sys/cpu
# gopkg.in/yaml.v3 v3.0.1 # gopkg.in/yaml.v3 v3.0.1
## explicit ## explicit
gopkg.in/yaml.v3 gopkg.in/yaml.v3
# maragu.dev/gomponents v1.0.0 # maragu.dev/gomponents v1.1.0
## explicit; go 1.18 ## explicit; go 1.18
maragu.dev/gomponents maragu.dev/gomponents
maragu.dev/gomponents/html maragu.dev/gomponents/html