Merge branch 'master' of https://gitlink.org.cn/JointCloud/pcm-coordinator
Former-commit-id: 801b0b3ade72279fa8c9861744d7daedde7c5a26
This commit is contained in:
commit
6f1edf393e
|
@ -13,9 +13,6 @@ PCM
|
|||
*.out
|
||||
coverage.txt
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
vendor/
|
||||
|
||||
# buf for protobuf
|
||||
buf.lock
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM golang:1.21.2-alpine3.18 AS builder
|
||||
FROM golang:1.22-alpine3.18 AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
|
|
@ -156,28 +156,26 @@ type AiInfo struct {
|
|||
}
|
||||
|
||||
type VmInfo struct {
|
||||
Id int64 `json:"id,omitempty"`
|
||||
TaskId int64 `json:"taskId,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
AdapterId int64 `json:"adapterId,omitempty,optional"`
|
||||
AdapterName string `json:"adapterName,omitempty,optional"`
|
||||
ClusterId int64 `json:"clusterId,omitempty,optional"`
|
||||
ClusterName string `json:"clusterName,omitempty,optional"`
|
||||
FlavorRef string `json:"flavorRef,omitempty"`
|
||||
ImageRef string `json:"imageRef,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Platform string `json:"platform,omitempty"`
|
||||
Description string `json:"description,omitempty"` // 描述
|
||||
AvailabilityZone string `json:"availabilityZone,omitempty"`
|
||||
MinCount int64 `json:"minCount,omitempty"`
|
||||
Uuid string `json:"uuid,omitempty"`
|
||||
StartTime string `json:"startTime,omitempty"`
|
||||
RunningTime string `json:"runningTime,omitempty"`
|
||||
Result string `json:"result,omitempty"`
|
||||
DeletedAt string `json:"deletedAt,omitempty"`
|
||||
VmName string `json:"vmName,omitempty"`
|
||||
Replicas int64 `json:"replicas,omitempty"`
|
||||
ServerId string `json:"serverId,omitempty"`
|
||||
Id int64 `json:"id,omitempty"`
|
||||
TaskId int64 `json:"taskId,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
AdapterId int64 `json:"adapterId,omitempty,optional"`
|
||||
AdapterName string `json:"adapterName,omitempty,optional"`
|
||||
ClusterId int64 `json:"clusterId,omitempty,optional"`
|
||||
ClusterName string `json:"clusterName,omitempty,optional"`
|
||||
FlavorRef string `json:"flavorRef,omitempty"`
|
||||
ImageRef string `json:"imageRef,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Platform string `json:"platform,omitempty"`
|
||||
MinCount int64 `json:"minCount,omitempty"`
|
||||
Uuid string `json:"uuid,omitempty"`
|
||||
StartTime string `json:"startTime,omitempty"`
|
||||
//RunningTime string `json:"runningTime,omitempty"`
|
||||
//Result string `json:"result,omitempty"`
|
||||
//DeletedAt string `json:"deletedAt,omitempty"`
|
||||
VmName string `json:"vmName,omitempty"`
|
||||
Replicas int64 `json:"replicas,omitempty"`
|
||||
ServerId string `json:"serverId,omitempty"`
|
||||
}
|
||||
|
||||
type ResourceStats struct {
|
||||
|
|
|
@ -720,6 +720,7 @@ type (
|
|||
Offset int32 `form:"offSet,optional"`
|
||||
/****************智算类型 ModelartsType parmas ********************/
|
||||
ModelArtsType string `form:"modelArtsType,optional"`
|
||||
Platform string `form:"platform,optional"`
|
||||
}
|
||||
)
|
||||
type ListServicesResp {
|
||||
|
@ -1149,7 +1150,7 @@ type (
|
|||
ProjectId string `json:"projectId" copier:"ProjectId"`
|
||||
Param ListNotebookParam `json:"param,optional" copier:"Param"`
|
||||
/****************智算类型 ModelartsType parmas ********************/
|
||||
ModelArtsType string `json:"modelArtsType,optional"`
|
||||
Platform string `json:"platform,optional"`
|
||||
}
|
||||
ListNotebookResp {
|
||||
Current int32 `json:"current,omitempty" copier:"Current"`
|
||||
|
@ -1205,7 +1206,7 @@ type (
|
|||
ProjectId string `json:"projectId" copier:"ProjectId"`
|
||||
Param StartNotebookParam `json:"param" copier:"Param"`
|
||||
/****************智算类型 ModelartsType parmas ********************/
|
||||
ModelArtsType string `json:"modelArtsType,optional"`
|
||||
Platform string `json:"platform,optional"`
|
||||
}
|
||||
StartNotebookResp {
|
||||
NotebookResp NotebookResp `json:"notebookResp" copier:"NotebookResp"`
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
syntax = "v1"
|
||||
|
||||
type (
|
||||
/******************image inference*************************/
|
||||
ModelTypesResp {
|
||||
ModelTypes []string `json:"types"`
|
||||
}
|
||||
|
||||
ModelNamesReq {
|
||||
Type string `form:"type"`
|
||||
}
|
||||
|
||||
ModelNamesResp {
|
||||
ModelNames []string `json:"models"`
|
||||
}
|
||||
/******************image inference*************************/
|
||||
|
||||
ImageInferenceReq {
|
||||
TaskName string `form:"taskName"`
|
||||
TaskDesc string `form:"taskDesc"`
|
||||
ModelName string `form:"modelName"`
|
||||
ModelType string `form:"modelType"`
|
||||
AdapterId string `form:"adapterId"`
|
||||
AiClusterIds []string `form:"aiClusterIds,optional"`
|
||||
ResourceType string `form:"resourceType,optional"`
|
||||
ComputeCard string `form:"card,optional"`
|
||||
Strategy string `form:"strategy"`
|
||||
StaticWeightMap map[string]int32 `form:"staticWeightMap,optional"`
|
||||
Params []string `form:"params,optional"`
|
||||
Envs []string `form:"envs,optional"`
|
||||
Cmd string `form:"cmd,optional"`
|
||||
Replica int32 `form:"replicas,optional"`
|
||||
}
|
||||
|
||||
ImageInferenceResp {
|
||||
InferResults []*ImageResult `json:"result"`
|
||||
}
|
||||
|
||||
ImageResult {
|
||||
ClusterId string `json:"clusterId"`
|
||||
ClusterName string `json:"clusterName"`
|
||||
ImageName string `json:"imageName"`
|
||||
Card string `json:"card"`
|
||||
ImageResult string `json:"imageResult"`
|
||||
}
|
||||
|
||||
)
|
|
@ -10,6 +10,7 @@ import (
|
|||
"storelink/pcm-storelink.api"
|
||||
"schedule/pcm-schedule.api"
|
||||
"monitoring/pcm-monitoring.api"
|
||||
"inference/inference.api"
|
||||
)
|
||||
|
||||
info(
|
||||
|
@ -914,6 +915,21 @@ service pcm {
|
|||
get /schedule/getClusterBalanceById/:adapterId/:clusterId (GetClusterBalanceByIdReq) returns (GetClusterBalanceByIdResp)
|
||||
}
|
||||
|
||||
@server(
|
||||
prefix: pcm/v1
|
||||
group: inference
|
||||
)
|
||||
service pcm {
|
||||
@handler ImageInferenceHandler
|
||||
post /inference/images (ImageInferenceReq) returns (ImageInferenceResp)
|
||||
|
||||
@handler ModelTypesHandler
|
||||
get /inference/modelTypes returns (ModelTypesResp)
|
||||
|
||||
@handler ModelNamesByTypeHandler
|
||||
get /inference/modelNames (ModelNamesReq) returns (ModelNamesResp)
|
||||
}
|
||||
|
||||
@server(
|
||||
prefix: pcm/v1
|
||||
group: dictionary
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
Name: pcm.core.api
|
||||
Host: 0.0.0.0
|
||||
Port: 8999
|
||||
MaxBytes: 524288000
|
||||
|
||||
Timeout: 50000
|
||||
|
||||
DB:
|
||||
DataSource: root:uJpLd6u-J?HC1@(10.206.0.12:3306)/pcm?parseTime=true&loc=Local
|
||||
# DataSource: root:uJpLd6u-J?HC1@(47.92.88.143:3306)/pcm?parseTime=true&loc=Local
|
||||
# DataSource: root:uJpLd6u-J?HC1@(47.92.88.143:3306)/pcm?parseTime=true&loc=Local
|
||||
Redis:
|
||||
Host: 10.206.0.12:6379
|
||||
Pass: redisPW123
|
||||
|
@ -24,16 +25,17 @@ THRpcConf:
|
|||
|
||||
#rpc
|
||||
ModelArtsRpcConf:
|
||||
target: nacos://10.206.0.12:8848/pcm.modelarts.rpc?timeout=30s&namespaceid=test&groupname=DEFAULT_GROUP&appName=pcm.core.api
|
||||
# Endpoints:
|
||||
# - 127.0.0.1:8888
|
||||
# target: nacos://10.206.0.12:8848/pcm.modelarts.rpc?timeout=30s&namespaceid=test&groupname=DEFAULT_GROUP&appName=pcm.core.api
|
||||
Endpoints:
|
||||
- 127.0.0.1:2002
|
||||
NonBlock: true
|
||||
Timeout: 20000
|
||||
|
||||
#rpc
|
||||
ModelArtsImgRpcConf:
|
||||
target: nacos://10.206.0.12:8848/pcm.modelarts.rpc?timeout=30s&namespaceid=test&groupname=DEFAULT_GROUP&appName=pcm.core.api
|
||||
# Endpoints:
|
||||
# - 127.0.0.1:8888
|
||||
# - 127.0.0.1:2002
|
||||
NonBlock: true
|
||||
|
||||
#rpc
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"fmt"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/zrpc"
|
||||
"gitlink.org.cn/JointCloud/pcm-ac/hpcacclient"
|
||||
hpcacclient "gitlink.org.cn/JointCloud/pcm-ac/hpcacclient"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/config"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/scheduler/service/collector"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/scheduler/service/executor"
|
||||
|
@ -90,7 +90,7 @@ func UpdateAiTaskStatus(svc *svc.ServiceContext, tasklist []*types.TaskModel) {
|
|||
var wg sync.WaitGroup
|
||||
for _, aitask := range aiTaskList {
|
||||
t := aitask
|
||||
if t.Status == constants.Completed || t.Status == constants.Failed {
|
||||
if t.Status == constants.Completed || t.Status == constants.Failed || t.JobId == "" {
|
||||
continue
|
||||
}
|
||||
wg.Add(1)
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package inference
|
||||
|
||||
import (
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/logic/inference"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/svc"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/types"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/pkg/repository/result"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func ImageInferenceHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.ImageInferenceReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
|
||||
l := inference.NewImageInferenceLogic(r.Context(), svcCtx)
|
||||
resp, err := l.ImageInfer(r, &req)
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package inference
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/logic/inference"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/svc"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/types"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/pkg/repository/result"
|
||||
)
|
||||
|
||||
func ModelNamesByTypeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req types.ModelNamesReq
|
||||
if err := httpx.Parse(r, &req); err != nil {
|
||||
result.ParamErrorResult(r, w, err)
|
||||
return
|
||||
}
|
||||
|
||||
l := inference.NewModelNamesByTypeLogic(r.Context(), svcCtx)
|
||||
resp, err := l.ModelNamesByType(&req)
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package inference
|
||||
|
||||
import (
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/logic/inference"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/svc"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/pkg/repository/result"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func ModelTypesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
l := inference.NewModelTypesLogic(r.Context(), svcCtx)
|
||||
resp, err := l.ModelTypes()
|
||||
result.HttpResult(r, w, resp, err)
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import (
|
|||
core "gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/handler/core"
|
||||
dictionary "gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/handler/dictionary"
|
||||
hpc "gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/handler/hpc"
|
||||
inference "gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/handler/inference"
|
||||
monitoring "gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/handler/monitoring"
|
||||
schedule "gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/handler/schedule"
|
||||
storage "gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/handler/storage"
|
||||
|
@ -1150,6 +1151,27 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
|
|||
rest.WithPrefix("/pcm/v1"),
|
||||
)
|
||||
|
||||
server.AddRoutes(
|
||||
[]rest.Route{
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/inference/images",
|
||||
Handler: inference.ImageInferenceHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodGet,
|
||||
Path: "/inference/modelTypes",
|
||||
Handler: inference.ModelTypesHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodGet,
|
||||
Path: "/inference/modelNames",
|
||||
Handler: inference.ModelNamesByTypeHandler(serverCtx),
|
||||
},
|
||||
},
|
||||
rest.WithPrefix("/pcm/v1"),
|
||||
)
|
||||
|
||||
server.AddRoutes(
|
||||
[]rest.Route{
|
||||
{
|
||||
|
|
|
@ -94,7 +94,7 @@ func (l *CommitGeneralTaskLogic) CommitGeneralTask(req *types.GeneralTaskReq) er
|
|||
Name: req.Name,
|
||||
CommitTime: time.Now(),
|
||||
YamlString: strings.Join(req.ReqBody, "\n---\n"),
|
||||
AdapterTypeDict: 0,
|
||||
AdapterTypeDict: "0",
|
||||
SynergyStatus: synergyStatus,
|
||||
Strategy: strategy,
|
||||
}
|
||||
|
|
|
@ -52,11 +52,6 @@ func (l *CommitVmTaskLogic) CommitVmTask(req *types.CommitVmTaskReq) (resp *type
|
|||
}()
|
||||
//TODO adapter
|
||||
adapterId, _ := strconv.ParseUint(req.AdapterIds[0], 10, 64)
|
||||
/*var clusters []*models.VmModel
|
||||
err2 := l.svcCtx.DbEngin.Raw("SELECT * FROM `t_cluster` where adapter_id in ? and id in ?", adapterId, req.ClusterIds).Scan(&clusters).Error
|
||||
if err2 != nil {
|
||||
logx.Errorf("CommitGeneralTask() => sql execution error: %v", err)
|
||||
}*/
|
||||
taskVm := models.TaskVm{}
|
||||
//TODO 执行策略返回集群跟 Replica
|
||||
opt := &option.VmOption{}
|
||||
|
@ -91,7 +86,7 @@ func (l *CommitVmTaskLogic) CommitVmTask(req *types.CommitVmTaskReq) (resp *type
|
|||
Name: req.Name,
|
||||
CommitTime: time.Now(),
|
||||
Description: "vm task",
|
||||
AdapterTypeDict: 3,
|
||||
AdapterTypeDict: "0",
|
||||
SynergyStatus: synergyStatus,
|
||||
Strategy: strategy,
|
||||
}
|
||||
|
|
|
@ -263,7 +263,7 @@ func (l *PageListTaskLogic) updateAiTaskStatus(tasklist []*types.TaskModel, ch c
|
|||
var wg sync.WaitGroup
|
||||
for _, aitask := range aiTaskList {
|
||||
t := aitask
|
||||
if t.Status == constants.Completed || t.Status == constants.Failed {
|
||||
if t.Status == constants.Completed || t.Status == constants.Failed || t.JobId == "" {
|
||||
continue
|
||||
}
|
||||
wg.Add(1)
|
||||
|
|
|
@ -59,8 +59,8 @@ func (l *PushTaskInfoLogic) PushTaskInfo(req *clientCore.PushTaskInfoReq) (*clie
|
|||
}
|
||||
case 02:
|
||||
for _, vmInfo := range req.VmInfoList {
|
||||
l.svcCtx.DbEngin.Exec("update task_vm set status = ?,start_time = ? where participant_id = ? and task_id = ? and name = ?",
|
||||
vmInfo.Status, vmInfo.StartTime, req.AdapterId, vmInfo.TaskId, vmInfo.Name)
|
||||
l.svcCtx.DbEngin.Exec("update task_vm set status = ?,start_time = ? where cluster_id = ? and task_id = ? and name = ?",
|
||||
vmInfo.Status, vmInfo.StartTime, vmInfo.ClusterId, vmInfo.TaskId, vmInfo.Name)
|
||||
noticeInfo := clientCore.NoticeInfo{
|
||||
TaskId: vmInfo.TaskId,
|
||||
AdapterId: vmInfo.AdapterId,
|
||||
|
@ -108,7 +108,7 @@ func (l *PushTaskInfoLogic) PushTaskInfo(req *clientCore.PushTaskInfoReq) (*clie
|
|||
func syncTask(gorm *gorm.DB, noticeInfo clientCore.NoticeInfo) {
|
||||
|
||||
var allStatus string
|
||||
tx := gorm.Raw("SELECT CONCAT_WS(',',GROUP_CONCAT(DISTINCT h.status) ,GROUP_CONCAT(DISTINCT a.status) ,GROUP_CONCAT(DISTINCT c.status))as status from task t left join task_hpc h on t.id = h.task_id left join task_cloud c on t.id = c.task_id left join task_ai a on t.id = a.task_id where t.id = ?", noticeInfo.TaskId).Scan(&allStatus)
|
||||
tx := gorm.Raw("SELECT CONCAT_WS(',',GROUP_CONCAT(DISTINCT h.status) ,GROUP_CONCAT(DISTINCT a.status) ,GROUP_CONCAT(DISTINCT c.status))as status from task t left join task_hpc h on t.id = h.task_id left join task_cloud c on t.id = c.task_id left join task_vm v on t.id = v.task_id left join task_ai a on t.id = a.task_id where t.id = ?", noticeInfo.TaskId).Scan(&allStatus)
|
||||
if tx.Error != nil {
|
||||
logx.Error(tx.Error)
|
||||
}
|
||||
|
|
|
@ -36,14 +36,14 @@ func (l *TaskDetailsLogic) TaskDetails(req *types.FId) (resp *types.TaskDetailsR
|
|||
var cList []*types.ClusterInfo
|
||||
var subList []*types.SubTaskInfo
|
||||
switch task.AdapterTypeDict {
|
||||
case 0:
|
||||
case "0":
|
||||
l.svcCtx.DbEngin.Table("task_cloud").Where("task_id", task.Id).Scan(&subList)
|
||||
if len(subList) <= 0 {
|
||||
l.svcCtx.DbEngin.Table("task_vm").Where("task_id", task.Id).Find(&subList)
|
||||
}
|
||||
case 1:
|
||||
case "1":
|
||||
l.svcCtx.DbEngin.Table("task_ai").Where("task_id", task.Id).Scan(&subList)
|
||||
case 2:
|
||||
case "2":
|
||||
l.svcCtx.DbEngin.Table("task_hpc").Where("task_id", task.Id).Scan(&subList)
|
||||
}
|
||||
for _, sub := range subList {
|
||||
|
|
|
@ -122,7 +122,7 @@ func (l *TaskListLogic) TaskList(req *types.TaskListReq) (resp *types.TaskListRe
|
|||
|
||||
func (l *TaskListLogic) updateAitaskStatus(tasks []models.Task, ch chan<- struct{}) {
|
||||
for _, task := range tasks {
|
||||
if task.AdapterTypeDict != 1 {
|
||||
if task.AdapterTypeDict != "1" {
|
||||
continue
|
||||
}
|
||||
if task.Status == constants.Succeeded {
|
||||
|
|
|
@ -40,7 +40,7 @@ func (l *CommitHpcTaskLogic) CommitHpcTask(req *types.CommitHpcTaskReq) (resp *t
|
|||
Strategy: 0,
|
||||
SynergyStatus: 0,
|
||||
CommitTime: time.Now(),
|
||||
AdapterTypeDict: 2,
|
||||
AdapterTypeDict: "2",
|
||||
}
|
||||
|
||||
// 保存任务数据到数据库
|
||||
|
|
|
@ -0,0 +1,412 @@
|
|||
package inference
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/scheduler/schedulers/option"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/scheduler/service/collector"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/scheduler/strategy"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/svc"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/types"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/pkg/constants"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/pkg/models"
|
||||
"math/rand"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ImageInferenceLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewImageInferenceLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ImageInferenceLogic {
|
||||
return &ImageInferenceLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *ImageInferenceLogic) ImageInference(req *types.ImageInferenceReq) (resp *types.ImageInferenceResp, err error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (l *ImageInferenceLogic) ImageInfer(r *http.Request, req *types.ImageInferenceReq) (resp *types.ImageInferenceResp, err error) {
|
||||
resp = &types.ImageInferenceResp{}
|
||||
opt := &option.InferOption{
|
||||
TaskName: req.TaskName,
|
||||
TaskDesc: req.TaskDesc,
|
||||
AdapterId: req.AdapterId,
|
||||
AiClusterIds: req.AiClusterIds,
|
||||
ModelName: req.ModelName,
|
||||
ModelType: req.ModelType,
|
||||
Strategy: req.Strategy,
|
||||
StaticWeightMap: req.StaticWeightMap,
|
||||
}
|
||||
|
||||
var ts []struct {
|
||||
imageResult *types.ImageResult
|
||||
file multipart.File
|
||||
}
|
||||
|
||||
uploadedFiles := r.MultipartForm.File
|
||||
|
||||
if len(uploadedFiles) == 0 {
|
||||
return nil, errors.New("Images does not exist")
|
||||
}
|
||||
|
||||
if len(uploadedFiles["images"]) == 0 {
|
||||
return nil, errors.New("Images does not exist")
|
||||
}
|
||||
|
||||
for _, header := range uploadedFiles["images"] {
|
||||
file, err := header.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
var ir types.ImageResult
|
||||
ir.ImageName = header.Filename
|
||||
t := struct {
|
||||
imageResult *types.ImageResult
|
||||
file multipart.File
|
||||
}{
|
||||
imageResult: &ir,
|
||||
file: file,
|
||||
}
|
||||
ts = append(ts, t)
|
||||
}
|
||||
|
||||
_, ok := l.svcCtx.Scheduler.AiService.AiCollectorAdapterMap[opt.AdapterId]
|
||||
if !ok {
|
||||
return nil, errors.New("AdapterId does not exist")
|
||||
}
|
||||
|
||||
var strat strategy.Strategy
|
||||
switch opt.Strategy {
|
||||
case strategy.STATIC_WEIGHT:
|
||||
strat = strategy.NewStaticWeightStrategy(opt.StaticWeightMap, int32(len(ts)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("no strategy has been chosen")
|
||||
}
|
||||
clusters, err := strat.Schedule()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results, err := infer(opt, clusters, ts, l.svcCtx, l.ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.InferResults = results
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func infer(opt *option.InferOption, clusters []*strategy.AssignedCluster, ts []struct {
|
||||
imageResult *types.ImageResult
|
||||
file multipart.File
|
||||
}, svcCtx *svc.ServiceContext, ctx context.Context) ([]*types.ImageResult, error) {
|
||||
|
||||
if clusters == nil || len(clusters) == 0 {
|
||||
return nil, errors.New("clusters is nil")
|
||||
}
|
||||
|
||||
for i := len(clusters) - 1; i >= 0; i-- {
|
||||
if clusters[i].Replicas == 0 {
|
||||
clusters = append(clusters[:i], clusters[i+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var cluster_ch = make(chan struct {
|
||||
urls []*collector.ImageInferUrl
|
||||
clusterId string
|
||||
clusterName string
|
||||
imageNum int32
|
||||
}, len(clusters))
|
||||
|
||||
var cs []struct {
|
||||
urls []*collector.ImageInferUrl
|
||||
clusterId string
|
||||
clusterName string
|
||||
imageNum int32
|
||||
}
|
||||
collectorMap := svcCtx.Scheduler.AiService.AiCollectorAdapterMap[opt.AdapterId]
|
||||
|
||||
//save task
|
||||
var synergystatus int64
|
||||
if len(clusters) > 1 {
|
||||
synergystatus = 1
|
||||
}
|
||||
|
||||
strategyCode, err := svcCtx.Scheduler.AiStorages.GetStrategyCode(opt.Strategy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
adapterName, err := svcCtx.Scheduler.AiStorages.GetAdapterNameById(opt.AdapterId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id, err := svcCtx.Scheduler.AiStorages.SaveTask(opt.TaskName, strategyCode, synergystatus, "11")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svcCtx.Scheduler.AiStorages.AddNoticeInfo(opt.AdapterId, adapterName, "", "", opt.TaskName, "create", "任务创建中")
|
||||
|
||||
//save taskai
|
||||
for _, c := range clusters {
|
||||
clusterName, _ := svcCtx.Scheduler.AiStorages.GetClusterNameById(c.ClusterId)
|
||||
opt.Replica = c.Replicas
|
||||
err := svcCtx.Scheduler.AiStorages.SaveAiTask(id, opt, adapterName, c.ClusterId, clusterName, "", constants.Saved, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, cluster := range clusters {
|
||||
wg.Add(1)
|
||||
c := cluster
|
||||
go func() {
|
||||
imageUrls, err := collectorMap[c.ClusterId].GetImageInferUrl(ctx, opt)
|
||||
if err != nil {
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
clusterName, _ := svcCtx.Scheduler.AiStorages.GetClusterNameById(c.ClusterId)
|
||||
|
||||
s := struct {
|
||||
urls []*collector.ImageInferUrl
|
||||
clusterId string
|
||||
clusterName string
|
||||
imageNum int32
|
||||
}{
|
||||
urls: imageUrls,
|
||||
clusterId: c.ClusterId,
|
||||
clusterName: clusterName,
|
||||
imageNum: c.Replicas,
|
||||
}
|
||||
|
||||
cluster_ch <- s
|
||||
wg.Done()
|
||||
return
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
close(cluster_ch)
|
||||
|
||||
for s := range cluster_ch {
|
||||
cs = append(cs, s)
|
||||
}
|
||||
|
||||
var aiTaskList []*models.TaskAi
|
||||
tx := svcCtx.DbEngin.Raw("select * from task_ai where `task_id` = ? ", id).Scan(&aiTaskList)
|
||||
if tx.Error != nil {
|
||||
return nil, tx.Error
|
||||
|
||||
}
|
||||
|
||||
//no cluster available
|
||||
if len(cs) == 0 {
|
||||
for _, t := range aiTaskList {
|
||||
t.Status = constants.Failed
|
||||
err := svcCtx.Scheduler.AiStorages.UpdateAiTask(t)
|
||||
if err != nil {
|
||||
logx.Errorf(tx.Error.Error())
|
||||
}
|
||||
}
|
||||
svcCtx.Scheduler.AiStorages.AddNoticeInfo(opt.AdapterId, adapterName, "", "", opt.TaskName, "failed", "任务失败")
|
||||
return nil, errors.New("image infer task failed")
|
||||
}
|
||||
|
||||
//change cluster status
|
||||
if len(clusters) != len(cs) {
|
||||
var acs []*strategy.AssignedCluster
|
||||
for _, cluster := range clusters {
|
||||
if contains(cs, cluster.ClusterId) {
|
||||
continue
|
||||
} else {
|
||||
var ac *strategy.AssignedCluster
|
||||
ac = cluster
|
||||
acs = append(acs, ac)
|
||||
}
|
||||
}
|
||||
|
||||
// update failed cluster status
|
||||
for _, ac := range acs {
|
||||
for _, t := range aiTaskList {
|
||||
if ac.ClusterId == strconv.Itoa(int(t.ClusterId)) {
|
||||
t.Status = constants.Failed
|
||||
err := svcCtx.Scheduler.AiStorages.UpdateAiTask(t)
|
||||
if err != nil {
|
||||
logx.Errorf(tx.Error.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var result_ch = make(chan *types.ImageResult, len(ts))
|
||||
var results []*types.ImageResult
|
||||
|
||||
var imageNumIdx int32 = 0
|
||||
var imageNumIdxEnd int32 = 0
|
||||
for _, c := range cs {
|
||||
new_images := make([]struct {
|
||||
imageResult *types.ImageResult
|
||||
file multipart.File
|
||||
}, len(ts))
|
||||
copy(new_images, ts)
|
||||
|
||||
imageNumIdxEnd = imageNumIdxEnd + c.imageNum
|
||||
new_images = new_images[imageNumIdx:imageNumIdxEnd]
|
||||
imageNumIdx = imageNumIdx + c.imageNum
|
||||
|
||||
wg.Add(len(new_images))
|
||||
go sendInferReq(new_images, c, &wg, result_ch)
|
||||
}
|
||||
wg.Wait()
|
||||
close(result_ch)
|
||||
|
||||
for s := range result_ch {
|
||||
results = append(results, s)
|
||||
}
|
||||
|
||||
sort.Slice(results, func(p, q int) bool {
|
||||
return results[p].ClusterName < results[q].ClusterName
|
||||
})
|
||||
|
||||
// update succeeded cluster status
|
||||
var successStatusCount int
|
||||
for _, c := range cs {
|
||||
for _, t := range aiTaskList {
|
||||
if c.clusterId == strconv.Itoa(int(t.ClusterId)) {
|
||||
t.Status = constants.Completed
|
||||
err := svcCtx.Scheduler.AiStorages.UpdateAiTask(t)
|
||||
if err != nil {
|
||||
logx.Errorf(tx.Error.Error())
|
||||
}
|
||||
successStatusCount++
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(cs) == successStatusCount {
|
||||
svcCtx.Scheduler.AiStorages.AddNoticeInfo(opt.AdapterId, adapterName, "", "", opt.TaskName, "failed", "任务失败")
|
||||
} else {
|
||||
svcCtx.Scheduler.AiStorages.AddNoticeInfo(opt.AdapterId, adapterName, "", "", opt.TaskName, "completed", "任务完成")
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func sendInferReq(images []struct {
|
||||
imageResult *types.ImageResult
|
||||
file multipart.File
|
||||
}, cluster struct {
|
||||
urls []*collector.ImageInferUrl
|
||||
clusterId string
|
||||
clusterName string
|
||||
imageNum int32
|
||||
}, wg *sync.WaitGroup, ch chan<- *types.ImageResult) {
|
||||
for _, image := range images {
|
||||
go func(t struct {
|
||||
imageResult *types.ImageResult
|
||||
file multipart.File
|
||||
}, c struct {
|
||||
urls []*collector.ImageInferUrl
|
||||
clusterId string
|
||||
clusterName string
|
||||
imageNum int32
|
||||
}) {
|
||||
if len(c.urls) == 1 {
|
||||
r, err := getInferResult(c.urls[0].Url, t.file, t.imageResult.ImageName)
|
||||
if err != nil {
|
||||
t.imageResult.ImageResult = err.Error()
|
||||
t.imageResult.ClusterName = c.clusterName
|
||||
t.imageResult.Card = c.urls[0].Card
|
||||
ch <- t.imageResult
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
t.imageResult.ImageResult = r
|
||||
t.imageResult.ClusterName = c.clusterName
|
||||
t.imageResult.Card = c.urls[0].Card
|
||||
|
||||
ch <- t.imageResult
|
||||
wg.Done()
|
||||
return
|
||||
} else {
|
||||
idx := rand.Intn(len(c.urls))
|
||||
r, err := getInferResult(c.urls[idx].Url, t.file, t.imageResult.ImageName)
|
||||
if err != nil {
|
||||
t.imageResult.ImageResult = err.Error()
|
||||
t.imageResult.ClusterName = c.clusterName
|
||||
t.imageResult.Card = c.urls[idx].Card
|
||||
ch <- t.imageResult
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
t.imageResult.ImageResult = r
|
||||
t.imageResult.ClusterName = c.clusterName
|
||||
t.imageResult.Card = c.urls[idx].Card
|
||||
|
||||
ch <- t.imageResult
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
}(image, cluster)
|
||||
}
|
||||
}
|
||||
|
||||
func getInferResult(url string, file multipart.File, fileName string) (string, error) {
|
||||
var res Res
|
||||
req := GetRestyRequest(10)
|
||||
_, err := req.
|
||||
SetFileReader("file", fileName, file).
|
||||
SetResult(&res).
|
||||
Post(url)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return res.Result, nil
|
||||
}
|
||||
|
||||
func GetRestyRequest(timeoutSeconds int64) *resty.Request {
|
||||
client := resty.New().SetTimeout(time.Duration(timeoutSeconds) * time.Second)
|
||||
request := client.R()
|
||||
return request
|
||||
}
|
||||
|
||||
type Res struct {
|
||||
Result string `json:"result"`
|
||||
}
|
||||
|
||||
func contains(cs []struct {
|
||||
urls []*collector.ImageInferUrl
|
||||
clusterId string
|
||||
clusterName string
|
||||
imageNum int32
|
||||
}, e string) bool {
|
||||
for _, c := range cs {
|
||||
if c.clusterId == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package inference
|
||||
|
||||
import (
|
||||
"context"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/storeLink"
|
||||
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/svc"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type ModelNamesByTypeLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewModelNamesByTypeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ModelNamesByTypeLogic {
|
||||
return &ModelNamesByTypeLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *ModelNamesByTypeLogic) ModelNamesByType(req *types.ModelNamesReq) (resp *types.ModelNamesResp, err error) {
|
||||
resp = &types.ModelNamesResp{}
|
||||
models, err := storeLink.GetModelNamesByType(req.Type)
|
||||
if err != nil {
|
||||
logx.Errorf("ModelNamesByType err: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
resp.ModelNames = models
|
||||
return resp, nil
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package inference
|
||||
|
||||
import (
|
||||
"context"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/storeLink"
|
||||
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/svc"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type ModelTypesLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewModelTypesLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ModelTypesLogic {
|
||||
return &ModelTypesLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *ModelTypesLogic) ModelTypes() (resp *types.ModelTypesResp, err error) {
|
||||
resp = &types.ModelTypesResp{}
|
||||
mTypes := storeLink.GetModelTypes()
|
||||
resp.ModelTypes = mTypes
|
||||
return resp, nil
|
||||
}
|
|
@ -71,7 +71,7 @@ func (l *ScheduleSubmitLogic) ScheduleSubmit(req *types.ScheduleReq) (resp *type
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id, err := l.svcCtx.Scheduler.AiStorages.SaveTask(req.AiOption.TaskName, strategyCode, synergystatus)
|
||||
id, err := l.svcCtx.Scheduler.AiStorages.SaveTask(req.AiOption.TaskName, strategyCode, synergystatus, "10")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -94,7 +94,7 @@ func (s *AiStorage) GetAiTasksByAdapterId(adapterId string) ([]*models.TaskAi, e
|
|||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *AiStorage) SaveTask(name string, strategyCode int64, synergyStatus int64) (int64, error) {
|
||||
func (s *AiStorage) SaveTask(name string, strategyCode int64, synergyStatus int64, aiType string) (int64, error) {
|
||||
// 构建主任务结构体
|
||||
taskModel := models.Task{
|
||||
Status: constants.Saved,
|
||||
|
@ -102,7 +102,8 @@ func (s *AiStorage) SaveTask(name string, strategyCode int64, synergyStatus int6
|
|||
Name: name,
|
||||
SynergyStatus: synergyStatus,
|
||||
Strategy: strategyCode,
|
||||
AdapterTypeDict: 1,
|
||||
AdapterTypeDict: "1",
|
||||
TaskTypeDict: aiType,
|
||||
CommitTime: time.Now(),
|
||||
}
|
||||
// 保存任务数据到数据库
|
||||
|
@ -113,9 +114,22 @@ func (s *AiStorage) SaveTask(name string, strategyCode int64, synergyStatus int6
|
|||
return taskModel.Id, nil
|
||||
}
|
||||
|
||||
func (s *AiStorage) SaveAiTask(taskId int64, option *option.AiOption, adapterName string, clusterId string, clusterName string, jobId string, status string, msg string) error {
|
||||
func (s *AiStorage) SaveAiTask(taskId int64, opt option.Option, adapterName string, clusterId string, clusterName string, jobId string, status string, msg string) error {
|
||||
var aiOpt *option.AiOption
|
||||
switch (opt).(type) {
|
||||
case *option.AiOption:
|
||||
aiOpt = (opt).(*option.AiOption)
|
||||
case *option.InferOption:
|
||||
inferOpt := (opt).(*option.InferOption)
|
||||
aiOpt = &option.AiOption{}
|
||||
aiOpt.TaskName = inferOpt.TaskName
|
||||
aiOpt.Replica = inferOpt.Replica
|
||||
aiOpt.AdapterId = inferOpt.AdapterId
|
||||
aiOpt.TaskType = inferOpt.ModelType
|
||||
aiOpt.StrategyName = inferOpt.Strategy
|
||||
}
|
||||
// 构建主任务结构体
|
||||
aId, err := strconv.ParseInt(option.AdapterId, 10, 64)
|
||||
aId, err := strconv.ParseInt(aiOpt.AdapterId, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -130,14 +144,14 @@ func (s *AiStorage) SaveAiTask(taskId int64, option *option.AiOption, adapterNam
|
|||
AdapterName: adapterName,
|
||||
ClusterId: cId,
|
||||
ClusterName: clusterName,
|
||||
Name: option.TaskName,
|
||||
Replica: int64(option.Replica),
|
||||
Name: aiOpt.TaskName,
|
||||
Replica: int64(aiOpt.Replica),
|
||||
JobId: jobId,
|
||||
TaskType: option.TaskType,
|
||||
Strategy: option.StrategyName,
|
||||
TaskType: aiOpt.TaskType,
|
||||
Strategy: aiOpt.StrategyName,
|
||||
Status: status,
|
||||
Msg: msg,
|
||||
Card: option.ComputeCard,
|
||||
Card: aiOpt.ComputeCard,
|
||||
CommitTime: time.Now(),
|
||||
}
|
||||
// 保存任务数据到数据库
|
||||
|
|
|
@ -222,7 +222,7 @@ func (as *AiScheduler) AssignTask(clusters []*strategy.AssignedCluster) (interfa
|
|||
synergystatus = 1
|
||||
}
|
||||
strategyCode, err := as.AiStorages.GetStrategyCode(as.option.StrategyName)
|
||||
taskId, err := as.AiStorages.SaveTask(as.option.TaskName, strategyCode, synergystatus)
|
||||
taskId, err := as.AiStorages.SaveTask(as.option.TaskName, strategyCode, synergystatus, "10")
|
||||
if err != nil {
|
||||
return nil, errors.New("database add failed: " + err.Error())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package option
|
||||
|
||||
type InferOption struct {
|
||||
TaskName string `json:"taskName"`
|
||||
TaskDesc string `json:"taskDesc"`
|
||||
ModelName string `json:"modelName"`
|
||||
ModelType string `json:"modelType"`
|
||||
AdapterId string `json:"adapterId"`
|
||||
AiClusterIds []string `json:"aiClusterIds"`
|
||||
ResourceType string `json:"resourceType"`
|
||||
ComputeCard string `json:"card"`
|
||||
Strategy string `json:"strategy"`
|
||||
StaticWeightMap map[string]int32 `json:"staticWeightMap,optional"`
|
||||
Params []string `json:"params,optional"`
|
||||
Envs []string `json:"envs,optional"`
|
||||
Cmd string `json:"cmd,optional"`
|
||||
Replica int32 `json:"replicas,optional"`
|
||||
}
|
||||
|
||||
func (a InferOption) GetOptionType() string {
|
||||
return AI_INFER
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
package option
|
||||
|
||||
const (
|
||||
AI = "ai"
|
||||
CLOUD = "cloud"
|
||||
HPC = "hpc"
|
||||
VM = "vm"
|
||||
AI_INFER = "ai_infer"
|
||||
AI = "ai"
|
||||
CLOUD = "cloud"
|
||||
HPC = "hpc"
|
||||
VM = "vm"
|
||||
)
|
||||
|
||||
type Option interface {
|
||||
|
|
|
@ -2,7 +2,7 @@ package service
|
|||
|
||||
import (
|
||||
"github.com/zeromicro/go-zero/zrpc"
|
||||
"gitlink.org.cn/JointCloud/pcm-ac/hpcacclient"
|
||||
hpcacclient "gitlink.org.cn/JointCloud/pcm-ac/hpcacclient"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/config"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/scheduler/database"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/scheduler/service/collector"
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package collector
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/scheduler/schedulers/option"
|
||||
)
|
||||
|
||||
type AiCollector interface {
|
||||
GetResourceStats(ctx context.Context) (*ResourceStats, error)
|
||||
|
@ -12,6 +15,12 @@ type AiCollector interface {
|
|||
UploadAlgorithmCode(ctx context.Context, resourceType string, card string, taskType string, dataset string, algorithm string, code string) error
|
||||
GetComputeCards(ctx context.Context) ([]string, error)
|
||||
GetUserBalance(ctx context.Context) (float64, error)
|
||||
GetImageInferUrl(ctx context.Context, option *option.InferOption) ([]*ImageInferUrl, error)
|
||||
}
|
||||
|
||||
type ImageInferUrl struct {
|
||||
Url string
|
||||
Card string
|
||||
}
|
||||
|
||||
type ResourceStats struct {
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
"gitlink.org.cn/JointCloud/pcm-modelarts/client/imagesservice"
|
||||
"gitlink.org.cn/JointCloud/pcm-modelarts/client/modelartsservice"
|
||||
"gitlink.org.cn/JointCloud/pcm-modelarts/modelarts"
|
||||
modelartsclient "gitlink.org.cn/JointCloud/pcm-modelarts/modelarts"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -376,3 +377,23 @@ func (m *ModelArtsLink) generateAlgorithmId(ctx context.Context, option *option.
|
|||
|
||||
return errors.New("failed to get AlgorithmId")
|
||||
}
|
||||
|
||||
func (m *ModelArtsLink) GetImageInferUrl(ctx context.Context, option *option.InferOption) ([]*collector.ImageInferUrl, error) {
|
||||
var imageUrls []*collector.ImageInferUrl
|
||||
urlReq := &modelartsclient.ImageReasoningUrlReq{
|
||||
ModelName: option.ModelName,
|
||||
Type: option.ModelType,
|
||||
Card: "npu",
|
||||
}
|
||||
urlResp, err := m.modelArtsRpc.ImageReasoningUrl(ctx, urlReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imageUrl := &collector.ImageInferUrl{
|
||||
Url: urlResp.Url,
|
||||
Card: "npu",
|
||||
}
|
||||
imageUrls = append(imageUrls, imageUrl)
|
||||
|
||||
return imageUrls, nil
|
||||
}
|
||||
|
|
|
@ -870,3 +870,30 @@ func setResourceIdByCard(option *option.AiOption, specs *octopus.GetResourceSpec
|
|||
}
|
||||
return errors.New("set ResourceId error")
|
||||
}
|
||||
|
||||
func (o *OctopusLink) GetImageInferUrl(ctx context.Context, option *option.InferOption) ([]*collector.ImageInferUrl, error) {
|
||||
req := &octopus.GetNotebookListReq{
|
||||
Platform: o.platform,
|
||||
PageIndex: o.pageIndex,
|
||||
PageSize: o.pageSize,
|
||||
}
|
||||
list, err := o.octopusRpc.GetNotebookList(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var imageUrls []*collector.ImageInferUrl
|
||||
for _, notebook := range list.Payload.GetNotebooks() {
|
||||
if strings.Contains(notebook.AlgorithmName, option.ModelName) {
|
||||
names := strings.Split(notebook.AlgorithmName, UNDERSCORE)
|
||||
imageUrl := &collector.ImageInferUrl{
|
||||
Url: DOMAIN + notebook.Tasks[0].Url + FORWARD_SLASH + "image",
|
||||
Card: names[2],
|
||||
}
|
||||
imageUrls = append(imageUrls, imageUrl)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return imageUrls, nil
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"gitlink.org.cn/JointCloud/pcm-ac/hpcAC"
|
||||
"gitlink.org.cn/JointCloud/pcm-ac/hpcacclient"
|
||||
hpcacclient "gitlink.org.cn/JointCloud/pcm-ac/hpcacclient"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/types"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/pkg/utils"
|
||||
"strconv"
|
||||
|
|
|
@ -18,7 +18,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"gitlink.org.cn/JointCloud/pcm-ac/hpcAC"
|
||||
"gitlink.org.cn/JointCloud/pcm-ac/hpcacclient"
|
||||
hpcacclient "gitlink.org.cn/JointCloud/pcm-ac/hpcacclient"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/scheduler/common"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/scheduler/schedulers/option"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/scheduler/service/collector"
|
||||
|
@ -98,7 +98,7 @@ type ShuguangAi struct {
|
|||
participantId int64
|
||||
}
|
||||
|
||||
func NewShuguangAi(aCRpc hpcacclient.HpcAC, name string, id int64) *ShuguangAi {
|
||||
func NewShuguangAi(aCRpc hpcAC.HpcACClient, name string, id int64) *ShuguangAi {
|
||||
return &ShuguangAi{aCRpc: aCRpc, platform: name, participantId: id}
|
||||
}
|
||||
|
||||
|
@ -729,3 +729,25 @@ func (s *ShuguangAi) generateParams(option *option.AiOption) error {
|
|||
|
||||
return errors.New("failed to set params")
|
||||
}
|
||||
|
||||
func (s *ShuguangAi) GetImageInferUrl(ctx context.Context, option *option.InferOption) ([]*collector.ImageInferUrl, error) {
|
||||
var imageUrls []*collector.ImageInferUrl
|
||||
|
||||
urlReq := &hpcAC.GetInferUrlReq{
|
||||
ModelName: option.ModelName,
|
||||
Type: option.ModelType,
|
||||
Card: "dcu",
|
||||
}
|
||||
|
||||
urlResp, err := s.aCRpc.GetInferUrl(ctx, urlReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imageUrl := &collector.ImageInferUrl{
|
||||
Url: urlResp.Url,
|
||||
Card: option.ComputeCard,
|
||||
}
|
||||
imageUrls = append(imageUrls, imageUrl)
|
||||
|
||||
return imageUrls, nil
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"gitlink.org.cn/JointCloud/pcm-ac/hpcAC"
|
||||
"gitlink.org.cn/JointCloud/pcm-ac/hpcacclient"
|
||||
hpcacclient "gitlink.org.cn/JointCloud/pcm-ac/hpcacclient"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/scheduler/common"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/scheduler/service/collector"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/types"
|
||||
|
@ -76,6 +76,9 @@ var (
|
|||
3: "制作完成",
|
||||
4: "制作失败",
|
||||
}
|
||||
ModelTypeMap = map[string][]string{
|
||||
"image_recognition": {"imagenet_resnet50"},
|
||||
}
|
||||
AITYPE = map[string]string{
|
||||
"1": OCTOPUS,
|
||||
"2": MODELARTS,
|
||||
|
@ -128,6 +131,22 @@ func GetResourceTypes() []string {
|
|||
return resourceTypes
|
||||
}
|
||||
|
||||
func GetModelTypes() []string {
|
||||
var mTypes []string
|
||||
for k, _ := range ModelTypeMap {
|
||||
mTypes = append(mTypes, k)
|
||||
}
|
||||
return mTypes
|
||||
}
|
||||
|
||||
func GetModelNamesByType(t string) ([]string, error) {
|
||||
_, ok := ModelTypeMap[t]
|
||||
if !ok {
|
||||
return nil, errors.New("model type does not exist")
|
||||
}
|
||||
return ModelTypeMap[t], nil
|
||||
}
|
||||
|
||||
func GetDatasetsNames(ctx context.Context, collectorMap map[string]collector.AiCollector) ([]string, error) {
|
||||
var wg sync.WaitGroup
|
||||
var errCh = make(chan interface{}, len(collectorMap))
|
||||
|
|
|
@ -21,7 +21,7 @@ import (
|
|||
"github.com/robfig/cron/v3"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/zrpc"
|
||||
"gitlink.org.cn/JointCloud/pcm-ac/hpcacclient"
|
||||
hpcacclient "gitlink.org.cn/JointCloud/pcm-ac/hpcacclient"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/config"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/scheduler"
|
||||
"gitlink.org.cn/JointCloud/pcm-coordinator/api/internal/scheduler/database"
|
||||
|
|
|
@ -1970,6 +1970,7 @@ type ListServicesReq struct {
|
|||
Limit int32 `form:"limit,optional"`
|
||||
Offset int32 `form:"offSet,optional"`
|
||||
ModelArtsType string `form:"modelArtsType,optional"`
|
||||
Platform string `form:"platform,optional"`
|
||||
}
|
||||
|
||||
type ListServicesResp struct {
|
||||
|
@ -2353,9 +2354,9 @@ type DeleteDataSetResp struct {
|
|||
}
|
||||
|
||||
type ListNotebookReq struct {
|
||||
ProjectId string `json:"projectId" copier:"ProjectId"`
|
||||
Param ListNotebookParam `json:"param,optional" copier:"Param"`
|
||||
ModelArtsType string `json:"modelArtsType,optional"`
|
||||
ProjectId string `json:"projectId" copier:"ProjectId"`
|
||||
Param ListNotebookParam `json:"param,optional" copier:"Param"`
|
||||
Platform string `json:"platform,optional"`
|
||||
}
|
||||
|
||||
type ListNotebookResp struct {
|
||||
|
@ -2411,10 +2412,10 @@ type CreateNotebookParam struct {
|
|||
}
|
||||
|
||||
type StartNotebookReq struct {
|
||||
Id string `json:"id" copier:"Id"`
|
||||
ProjectId string `json:"projectId" copier:"ProjectId"`
|
||||
Param StartNotebookParam `json:"param" copier:"Param"`
|
||||
ModelArtsType string `json:"modelArtsType,optional"`
|
||||
Id string `json:"id" copier:"Id"`
|
||||
ProjectId string `json:"projectId" copier:"ProjectId"`
|
||||
Param StartNotebookParam `json:"param" copier:"Param"`
|
||||
Platform string `json:"platform,optional"`
|
||||
}
|
||||
|
||||
type StartNotebookResp struct {
|
||||
|
@ -5868,3 +5869,44 @@ type Link struct {
|
|||
type Category struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type ModelTypesResp struct {
|
||||
ModelTypes []string `json:"types"`
|
||||
}
|
||||
|
||||
type ModelNamesReq struct {
|
||||
Type string `form:"type"`
|
||||
}
|
||||
|
||||
type ModelNamesResp struct {
|
||||
ModelNames []string `json:"models"`
|
||||
}
|
||||
|
||||
type ImageInferenceReq struct {
|
||||
TaskName string `form:"taskName"`
|
||||
TaskDesc string `form:"taskDesc"`
|
||||
ModelName string `form:"modelName"`
|
||||
ModelType string `form:"modelType"`
|
||||
AdapterId string `form:"adapterId"`
|
||||
AiClusterIds []string `form:"aiClusterIds,optional"`
|
||||
ResourceType string `form:"resourceType,optional"`
|
||||
ComputeCard string `form:"card,optional"`
|
||||
Strategy string `form:"strategy"`
|
||||
StaticWeightMap map[string]int32 `form:"staticWeightMap,optional"`
|
||||
Params []string `form:"params,optional"`
|
||||
Envs []string `form:"envs,optional"`
|
||||
Cmd string `form:"cmd,optional"`
|
||||
Replica int32 `form:"replicas,optional"`
|
||||
}
|
||||
|
||||
type ImageInferenceResp struct {
|
||||
InferResults []*ImageResult `json:"result"`
|
||||
}
|
||||
|
||||
type ImageResult struct {
|
||||
ClusterId string `json:"clusterId"`
|
||||
ClusterName string `json:"clusterName"`
|
||||
ImageName string `json:"imageName"`
|
||||
Card string `json:"card"`
|
||||
ImageResult string `json:"imageResult"`
|
||||
}
|
||||
|
|
101
go.mod
101
go.mod
|
@ -1,6 +1,8 @@
|
|||
module gitlink.org.cn/JointCloud/pcm-coordinator
|
||||
|
||||
go 1.21
|
||||
go 1.22.0
|
||||
|
||||
toolchain go1.22.4
|
||||
|
||||
retract v0.1.20-0.20240319015239-6ae13da05255
|
||||
|
||||
|
@ -10,36 +12,36 @@ require (
|
|||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
github.com/go-resty/resty/v2 v2.12.0
|
||||
github.com/go-sql-driver/mysql v1.7.1
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/jinzhu/copier v0.4.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.71.2
|
||||
github.com/prometheus/alertmanager v0.27.0
|
||||
github.com/prometheus/client_golang v1.19.0
|
||||
github.com/prometheus/common v0.52.2
|
||||
github.com/prometheus/client_golang v1.19.1
|
||||
github.com/prometheus/common v0.54.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/zeromicro/go-zero v1.6.3
|
||||
gitlink.org.cn/JointCloud/pcm-ac v0.0.0-20240426095603-549fefd8bece
|
||||
gitlink.org.cn/JointCloud/pcm-kubernetes v0.0.0-20240301071143-347480abff2c
|
||||
gitlink.org.cn/JointCloud/pcm-modelarts v0.0.0-20240515005224-689bb339a9c9
|
||||
github.com/zeromicro/go-zero v1.6.5
|
||||
gitlink.org.cn/JointCloud/pcm-ac v0.0.0-20240619113316-c0186ee7b60c
|
||||
gitlink.org.cn/JointCloud/pcm-modelarts v0.0.0-20240620065702-5dcad373c1fe
|
||||
gitlink.org.cn/JointCloud/pcm-octopus v0.0.0-20240510133934-6a5526289b35
|
||||
gitlink.org.cn/JointCloud/pcm-openstack v0.0.0-20240403033338-e7edabad4203
|
||||
gitlink.org.cn/JointCloud/pcm-slurm v0.0.0-20240301080743-8b94bbaf57f5
|
||||
gitlink.org.cn/jcce-pcm/pcm-participant-ceph v0.0.0-20230904090036-24fc730ec87d
|
||||
go.opentelemetry.io/otel/trace v1.25.0
|
||||
go.opentelemetry.io/otel/trace v1.27.0
|
||||
gonum.org/v1/gonum v0.11.0
|
||||
google.golang.org/grpc v1.63.0
|
||||
google.golang.org/protobuf v1.33.0
|
||||
google.golang.org/grpc v1.64.0
|
||||
google.golang.org/protobuf v1.34.2
|
||||
gorm.io/datatypes v1.2.0
|
||||
gorm.io/driver/mysql v1.5.2
|
||||
gorm.io/gorm v1.25.5
|
||||
k8s.io/apimachinery v0.29.3
|
||||
k8s.io/client-go v0.29.3
|
||||
k8s.io/apimachinery v0.30.2
|
||||
k8s.io/client-go v0.30.2
|
||||
sigs.k8s.io/yaml v1.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/alecthomas/kingpin/v2 v2.4.0 // indirect
|
||||
github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect
|
||||
github.com/armon/go-metrics v0.4.1 // indirect
|
||||
|
@ -53,12 +55,12 @@ require (
|
|||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.12.0 // indirect
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.12.1 // indirect
|
||||
github.com/fatih/color v1.17.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/go-kit/log v0.2.1 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/analysis v0.22.2 // indirect
|
||||
github.com/go-openapi/errors v0.21.0 // indirect
|
||||
|
@ -79,9 +81,8 @@ require (
|
|||
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
github.com/hashicorp/go-msgpack v0.5.3 // indirect
|
||||
|
@ -109,59 +110,57 @@ require (
|
|||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
|
||||
github.com/oklog/run v1.1.0 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.14.0 // indirect
|
||||
github.com/onsi/gomega v1.30.0 // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/openzipkin/zipkin-go v0.4.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.0 // indirect
|
||||
github.com/openzipkin/zipkin-go v0.4.3 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common/sigv4 v0.1.0 // indirect
|
||||
github.com/prometheus/exporter-toolkit v0.11.0 // indirect
|
||||
github.com/prometheus/procfs v0.13.0 // indirect
|
||||
github.com/redis/go-redis/v9 v9.5.1 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/redis/go-redis/v9 v9.5.3 // indirect
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
|
||||
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
|
||||
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
|
||||
github.com/xlab/treeprint v1.2.0 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.13 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.13 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.5.13 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.14 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.14 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.5.14 // indirect
|
||||
go.mongodb.org/mongo-driver v1.13.1 // indirect
|
||||
go.opentelemetry.io/otel v1.25.0 // indirect
|
||||
go.opentelemetry.io/otel v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.25.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.25.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.25.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.25.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.25.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.25.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.27.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.27.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||
go.uber.org/automaxprocs v1.5.3 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/crypto v0.22.0 // indirect
|
||||
golang.org/x/crypto v0.24.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect
|
||||
golang.org/x/mod v0.15.0 // indirect
|
||||
golang.org/x/net v0.24.0 // indirect
|
||||
golang.org/x/oauth2 v0.19.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/term v0.19.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/net v0.26.0 // indirect
|
||||
golang.org/x/oauth2 v0.21.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/term v0.21.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.18.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/api v0.29.3 // indirect
|
||||
k8s.io/klog/v2 v2.120.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20240403164606-bc84c2ddaf99 // indirect
|
||||
k8s.io/utils v0.0.0-20240310230437-4693a0247e57 // indirect
|
||||
k8s.io/api v0.30.2 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20240620174524-b456828f718b // indirect
|
||||
k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
)
|
||||
|
|
218
go.sum
218
go.sum
|
@ -31,6 +31,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
|
|||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||
|
@ -49,8 +51,8 @@ github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 h1:ez/4by2iGztzR4
|
|||
github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
|
||||
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE=
|
||||
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||
github.com/alicebob/miniredis/v2 v2.31.1 h1:7XAt0uUg3DtwEKW5ZAGa+K7FZV2DdKQo5K/6TTnfX8Y=
|
||||
github.com/alicebob/miniredis/v2 v2.31.1/go.mod h1:UB/T2Uztp7MlFSDakaX1sTXUv5CASoprx0wulRT6HBg=
|
||||
github.com/alicebob/miniredis/v2 v2.32.1 h1:Bz7CciDnYSaa0mX5xODh6GUITRSx+cVhjNoOR4JssBo=
|
||||
github.com/alicebob/miniredis/v2 v2.32.1/go.mod h1:AqkLNAfUm0K07J28hnAyyQKf/x0YkCY/g5DCtuL01Mw=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
|
||||
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
|
||||
|
@ -94,14 +96,14 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
|||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk=
|
||||
github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU=
|
||||
github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
|
@ -120,8 +122,8 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG
|
|||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/analysis v0.22.2 h1:ZBmNoP2h5omLKr/srIC9bfqrUGzT6g6gNv03HE9Vpj0=
|
||||
|
@ -149,11 +151,12 @@ github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq
|
|||
github.com/go-resty/resty/v2 v2.12.0 h1:rsVL8P90LFvkUYq/V5BTVe203WfRIU4gvcf+yfzJzGA=
|
||||
github.com/go-resty/resty/v2 v2.12.0/go.mod h1:o0yGPrkS3lOe1+eFajk6kBW8ScXzwU3hD69/gt2yB/0=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
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/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
|
@ -227,15 +230,15 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
|
|||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 h1:WzfWbQz/Ze8v6l++GGbGNFZnUShVpP/0xffCPLL+ax8=
|
||||
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg=
|
||||
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
|
@ -270,8 +273,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
|
|||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.5.3 h1:Ces6/M3wbDXYpM8JyyPD57ivTtJACFZJd885pdIaV2s=
|
||||
github.com/jackc/pgx/v5 v5.5.3/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
||||
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||
|
@ -355,19 +358,19 @@ github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
|||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY=
|
||||
github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw=
|
||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g=
|
||||
github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
|
||||
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
|
||||
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
|
||||
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
|
||||
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||
github.com/openzipkin/zipkin-go v0.4.2 h1:zjqfqHjUpPmB3c1GlCvvgsM1G4LkvqQbBDueDOCg/jA=
|
||||
github.com/openzipkin/zipkin-go v0.4.2/go.mod h1:ZeVkFjuuBiSy13y8vpSDCjMi9GoI3hPpCJSBx/EYFhY=
|
||||
github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg=
|
||||
github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo=
|
||||
github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
|
@ -386,8 +389,8 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn
|
|||
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
|
||||
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
|
||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
|
@ -399,8 +402,8 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b
|
|||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/common v0.52.2 h1:LW8Vk7BccEdONfrJBDffQGRtpSzi5CQaRZGtboOO2ck=
|
||||
github.com/prometheus/common v0.52.2/go.mod h1:lrWtQx+iDfn2mbH5GUzlH9TSHyfZpHkSiG1W7y3sF2Q=
|
||||
github.com/prometheus/common v0.54.0 h1:ZlZy0BgJhTwVZUn7dLOkwCZHUkrAqd3WYtcFCWnM1D8=
|
||||
github.com/prometheus/common v0.54.0/go.mod h1:/TQgMJP5CuVYveyT7n/0Ix8yLNNXy9yRSkhnLTHPDIQ=
|
||||
github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4=
|
||||
github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI=
|
||||
github.com/prometheus/exporter-toolkit v0.11.0 h1:yNTsuZ0aNCNFQ3aFTD2uhPOvr4iD7fdBvKPAEGkNf+g=
|
||||
|
@ -410,15 +413,15 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
|
|||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o=
|
||||
github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g=
|
||||
github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8=
|
||||
github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/redis/go-redis/v9 v9.5.3 h1:fOAp1/uJG+ZtcITgZOfYFmTKPE7n4Vclj1wZFgRciUU=
|
||||
github.com/redis/go-redis/v9 v9.5.3/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs=
|
||||
|
@ -462,16 +465,14 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
|||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE=
|
||||
github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
github.com/zeromicro/go-zero v1.6.3 h1:OL0NnHD5LdRNDolfcK9vUkJt7K8TcBE3RkzfM8poOVw=
|
||||
github.com/zeromicro/go-zero v1.6.3/go.mod h1:XZL435ZxVi9MSXXtw2MRQhHgx6OoX3++MRMOE9xU70c=
|
||||
gitlink.org.cn/JointCloud/pcm-ac v0.0.0-20240426095603-549fefd8bece h1:W3yBnvAVV8dlRNQKYD6Mf8ySRrYsP0tPk7JjvqZzNHQ=
|
||||
gitlink.org.cn/JointCloud/pcm-ac v0.0.0-20240426095603-549fefd8bece/go.mod h1:w3Nb5TNymCItQ7K3x4Q0JLuoq9OerwAzAWT2zsPE9Xo=
|
||||
gitlink.org.cn/JointCloud/pcm-kubernetes v0.0.0-20240301071143-347480abff2c h1:2Wl/hvaSFjh6fmCSIQhjkr9llMRREQeqcXNLZ/HPY18=
|
||||
gitlink.org.cn/JointCloud/pcm-kubernetes v0.0.0-20240301071143-347480abff2c/go.mod h1:lSRfGs+PxFvw7CcndHWRd6UlLlGrZn0b0hp5cfaMNGw=
|
||||
gitlink.org.cn/JointCloud/pcm-modelarts v0.0.0-20240515005224-689bb339a9c9 h1:FRtOtI9vDFHcyPUdc4PL95CFi/DFk+HXT6JNTf/91d8=
|
||||
gitlink.org.cn/JointCloud/pcm-modelarts v0.0.0-20240515005224-689bb339a9c9/go.mod h1:2WC5tDApfQNNIBfDNYwdaQiXhfCsG2n03P3ZxX9p9O4=
|
||||
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
github.com/zeromicro/go-zero v1.6.5 h1:JgsBa25/knnEL7+KQksbwktudIkNQvaAin0nisVgnSA=
|
||||
github.com/zeromicro/go-zero v1.6.5/go.mod h1:XjbssEVEzFKueAh0Fie5kNf+cRqFlQQk46fY9WgEGaM=
|
||||
gitlink.org.cn/JointCloud/pcm-ac v0.0.0-20240619113316-c0186ee7b60c h1:HrU3GPuHWTAajQxbkDFygsAm/HURJFGT2yckUMdOdGM=
|
||||
gitlink.org.cn/JointCloud/pcm-ac v0.0.0-20240619113316-c0186ee7b60c/go.mod h1:3eECiw9O2bIFkkePlloKyLNXiqBAhOxNrDoGaaGseGY=
|
||||
gitlink.org.cn/JointCloud/pcm-modelarts v0.0.0-20240620065702-5dcad373c1fe h1:teAWL7sJszDb1ZA7uptrzPSwJ1OIV840Q1/nrrDsx7E=
|
||||
gitlink.org.cn/JointCloud/pcm-modelarts v0.0.0-20240620065702-5dcad373c1fe/go.mod h1:/eOmBFZKWGoabG3sRVkVvIbLwsd2631k4jkUBR6x1AA=
|
||||
gitlink.org.cn/JointCloud/pcm-octopus v0.0.0-20240510133934-6a5526289b35 h1:E2QfpS3Y0FjR8Zyv5l2Ti/2NetQFqHG66c8+T/+J1u0=
|
||||
gitlink.org.cn/JointCloud/pcm-octopus v0.0.0-20240510133934-6a5526289b35/go.mod h1:QOD5+/l2D+AYBjF2h5T0mdJyfGAmF78QmeKdbBXbjLQ=
|
||||
gitlink.org.cn/JointCloud/pcm-openstack v0.0.0-20240403033338-e7edabad4203 h1:s6PsZ1+bev294IWdZRlV7mnOwI1+UzFcldVW/BqhQzI=
|
||||
|
@ -480,12 +481,12 @@ gitlink.org.cn/JointCloud/pcm-slurm v0.0.0-20240301080743-8b94bbaf57f5 h1:+/5vnz
|
|||
gitlink.org.cn/JointCloud/pcm-slurm v0.0.0-20240301080743-8b94bbaf57f5/go.mod h1:97AlUXN13g9UN3+9/DzCHpeoU5sbdyv0IQuTEHNexzQ=
|
||||
gitlink.org.cn/jcce-pcm/pcm-participant-ceph v0.0.0-20230904090036-24fc730ec87d h1:DHjl/rLuH2gKYtY0MKMGNQDHFT12APg25RlMUQo+tHk=
|
||||
gitlink.org.cn/jcce-pcm/pcm-participant-ceph v0.0.0-20230904090036-24fc730ec87d/go.mod h1:r/KLzUpupCV5jdxSfgDhc2pVjP0fBi3VhAWRttsBn30=
|
||||
go.etcd.io/etcd/api/v3 v3.5.13 h1:8WXU2/NBge6AUF1K1gOexB6e07NgsN1hXK0rSTtgSp4=
|
||||
go.etcd.io/etcd/api/v3 v3.5.13/go.mod h1:gBqlqkcMMZMVTMm4NDZloEVJzxQOQIls8splbqBDa0c=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.13 h1:RVZSAnWWWiI5IrYAXjQorajncORbS0zI48LQlE2kQWg=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.13/go.mod h1:XxHT4u1qU12E2+po+UVPrEeL94Um6zL58ppuJWXSAB8=
|
||||
go.etcd.io/etcd/client/v3 v3.5.13 h1:o0fHTNJLeO0MyVbc7I3fsCf6nrOqn5d+diSarKnB2js=
|
||||
go.etcd.io/etcd/client/v3 v3.5.13/go.mod h1:cqiAeY8b5DEEcpxvgWKsbLIWNM/8Wy2xJSDMtioMcoI=
|
||||
go.etcd.io/etcd/api/v3 v3.5.14 h1:vHObSCxyB9zlF60w7qzAdTcGaglbJOpSj1Xj9+WGxq0=
|
||||
go.etcd.io/etcd/api/v3 v3.5.14/go.mod h1:BmtWcRlQvwa1h3G2jvKYwIQy4PkHlDej5t7uLMUdJUU=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.14 h1:SaNH6Y+rVEdxfpA2Jr5wkEvN6Zykme5+YnbCkxvuWxQ=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.14/go.mod h1:8uMgAokyG1czCtIdsq+AGyYQMvpIKnSvPjFMunkgeZI=
|
||||
go.etcd.io/etcd/client/v3 v3.5.14 h1:CWfRs4FDaDoSz81giL7zPpZH2Z35tbOrAJkkjMqOupg=
|
||||
go.etcd.io/etcd/client/v3 v3.5.14/go.mod h1:k3XfdV/VIHy/97rqWjoUzrj9tk7GgJGH9J8L4dNXmAk=
|
||||
go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk=
|
||||
go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
|
@ -493,28 +494,28 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
|||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k=
|
||||
go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg=
|
||||
go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg=
|
||||
go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ=
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0 h1:dT33yIHtmsqpixFsSQPwNeY5drM9wTcoL8h0FWF4oGM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0/go.mod h1:h95q0LBGh7hlAC08X2DhSeyIG02YQ0UyioTCVAqRPmc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.25.0 h1:vOL89uRfOCCNIjkisd0r7SEdJF3ZJFyCNY34fdZs8eU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.25.0/go.mod h1:8GlBGcDk8KKi7n+2S4BT/CPZQYH3erLu0/k64r1MYgo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.25.0 h1:Mbi5PKN7u322woPa85d7ebZ+SOvEoPvoiBu+ryHWgfA=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.25.0/go.mod h1:e7ciERRhZaOZXVjx5MiL8TK5+Xv7G5Gv5PA2ZDEJdL8=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.25.0 h1:0vZZdECYzhTt9MKQZ5qQ0V+J3MFu4MQaQ3COfugF+FQ=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.25.0/go.mod h1:e7iXx3HjaSSBXfy9ykVUlupS2Vp7LBIBuT21ousM2Hk=
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.25.0 h1:iLzdsOsstvim/54ymA2BhEN4+1NbsvwGvOhSkQy2TaY=
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.25.0/go.mod h1:3QXxNo6ace1QZX6pSHEzGKKESVdjQxXR03FcIH7dNGs=
|
||||
go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA=
|
||||
go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s=
|
||||
go.opentelemetry.io/otel/sdk v1.25.0 h1:PDryEJPC8YJZQSyLY5eqLeafHtG+X7FWnf3aXMtxbqo=
|
||||
go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw=
|
||||
go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM=
|
||||
go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I=
|
||||
go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI=
|
||||
go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 h1:QY7/0NeRPKlzusf40ZE4t1VlMKbqSNT7cJRYzWuja0s=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0/go.mod h1:HVkSiDhTM9BoUJU8qE6j2eSWLLXvi1USXjyd2BXT8PY=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.27.0 h1:/0YaXu3755A/cFbtXp+21lkXgI0QE5avTWA2HjU9/WE=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.27.0/go.mod h1:m7SFxp0/7IxmJPLIY3JhOcU9CoFzDaCPL6xxQIxhA+o=
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.27.0 h1:aXcxb7F6ZDC1o2Z52LDfS2g6M2FB5CrxdR2gzY4QRNs=
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.27.0/go.mod h1:+WMURoi4KmVB7ypbFPx3xtZTWen2Ca3lRK9u6DVTO5M=
|
||||
go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik=
|
||||
go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak=
|
||||
go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI=
|
||||
go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A=
|
||||
go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw=
|
||||
go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4=
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
||||
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
|
||||
|
@ -536,8 +537,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
|||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -573,8 +574,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -615,16 +616,16 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
|
||||
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
|
||||
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
|
||||
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -638,8 +639,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -690,16 +691,16 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -710,8 +711,9 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
@ -763,8 +765,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
|
|||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
|
||||
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -822,10 +824,10 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
|
|||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda h1:b6F6WIV4xHHD0FA4oIyzU6mHWg2WI2X1RBehwa5QN38=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda/go.mod h1:AHcE/gZH76Bk/ROZhQphlRoWo5xKDEtz3eVEO1LfA8c=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 h1:MuYw1wJzT+ZkybKfaOXKp5hJiZDn2iHaXRw0mRYdHSc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4/go.mod h1:px9SlOOZBg1wM1zdnr8jEL4CNGUBZ+ZKYtNPApNQc4c=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 h1:Di6ANFilr+S60a4S61ZM00vLdw0IrQOSMS2/6mrnOU0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
|
@ -838,8 +840,8 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
|
|||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.63.0 h1:WjKe+dnvABXyPJMD7KDNLxtoGk5tgk+YFWN6cBWjZE8=
|
||||
google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
||||
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
@ -851,8 +853,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
|
|||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
@ -897,18 +899,18 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
|||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw=
|
||||
k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80=
|
||||
k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU=
|
||||
k8s.io/apimachinery v0.29.3/go.mod h1:hx/S4V2PNW4OMg3WizRrHutyB5la0iCUbZym+W0EQIU=
|
||||
k8s.io/client-go v0.29.3 h1:R/zaZbEAxqComZ9FHeQwOh3Y1ZUs7FaHKZdQtIc2WZg=
|
||||
k8s.io/client-go v0.29.3/go.mod h1:tkDisCvgPfiRpxGnOORfkljmS+UrW+WtXAy2fTvXJB0=
|
||||
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
|
||||
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kube-openapi v0.0.0-20240403164606-bc84c2ddaf99 h1:w6nThEmGo9zcL+xH1Tu6pjxJ3K1jXFW+V0u4peqN8ks=
|
||||
k8s.io/kube-openapi v0.0.0-20240403164606-bc84c2ddaf99/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
|
||||
k8s.io/utils v0.0.0-20240310230437-4693a0247e57 h1:gbqbevonBh57eILzModw6mrkbwM0gQBEuevE/AaBsHY=
|
||||
k8s.io/utils v0.0.0-20240310230437-4693a0247e57/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
k8s.io/api v0.30.2 h1:+ZhRj+28QT4UOH+BKznu4CBgPWgkXO7XAvMcMl0qKvI=
|
||||
k8s.io/api v0.30.2/go.mod h1:ULg5g9JvOev2dG0u2hig4Z7tQ2hHIuS+m8MNZ+X6EmI=
|
||||
k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg=
|
||||
k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
|
||||
k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50=
|
||||
k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kube-openapi v0.0.0-20240620174524-b456828f718b h1:Q9xmGWBvOGd8UJyccgpYlLosk/JlfP3xQLNkQlHJeXw=
|
||||
k8s.io/kube-openapi v0.0.0-20240620174524-b456828f718b/go.mod h1:UxDHUPsUwTOOxSU+oXURfFBcAS6JwiRXTYqYwfuGowc=
|
||||
k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak=
|
||||
k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
|
|
@ -49,7 +49,8 @@ type (
|
|||
Result string `db:"result"` // 作业结果
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
NsID string `db:"ns_id"`
|
||||
AdapterTypeDict int `db:"adapter_type_dict"` //任务类型(对应字典表的值)
|
||||
AdapterTypeDict string `db:"adapter_type_dict"` //任务类型(对应字典表的值)
|
||||
TaskTypeDict string `db:"task_type_dict"`
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -35,28 +35,26 @@ type (
|
|||
}
|
||||
|
||||
TaskVm struct {
|
||||
Id int64 `db:"id"` // id
|
||||
TaskId int64 `db:"task_id"` // 任务id
|
||||
Name string `db:"name"` // 任务名称
|
||||
AdapterId int64 `db:"adapter_id"` // 执行任务的适配器id
|
||||
AdapterName string `db:"adapter_name"` // 适配器名称
|
||||
ClusterId int64 `db:"cluster_id"` // 执行任务的集群id
|
||||
ClusterName string `db:"cluster_name"` // 集群名称
|
||||
FlavorRef string `db:"flavor_ref"` // 规格索引
|
||||
ImageRef string `db:"image_ref"` // 镜像索引
|
||||
Status string `db:"status"` // 状态
|
||||
Platform string `db:"platform"` // 平台
|
||||
Description string `db:"description"` // 描述
|
||||
AvailabilityZone string `db:"availability_zone"`
|
||||
MinCount int64 `db:"min_count"` // 数量
|
||||
Uuid string `db:"uuid"` // 网络id
|
||||
StartTime string `db:"start_time"` // 开始时间
|
||||
RunningTime string `db:"running_time"` // 运行时间
|
||||
Result string `db:"result"` // 运行结果
|
||||
Remark string `db:"remark"` // 备注
|
||||
DeletedAt string `db:"deleted_at"` // 删除时间
|
||||
VmName string `db:"vm_name"` // 虚拟机名称
|
||||
Replicas int64 `db:"replicas"` // 副本数
|
||||
Id int64 `db:"id"` // id
|
||||
TaskId int64 `db:"task_id"` // 任务id
|
||||
Name string `db:"name"` // 任务名称
|
||||
AdapterId int64 `db:"adapter_id"` // 执行任务的适配器id
|
||||
AdapterName string `db:"adapter_name"` // 适配器名称
|
||||
ClusterId int64 `db:"cluster_id"` // 执行任务的集群id
|
||||
ClusterName string `db:"cluster_name"` // 集群名称
|
||||
FlavorRef string `db:"flavor_ref"` // 规格索引
|
||||
ImageRef string `db:"image_ref"` // 镜像索引
|
||||
Status string `db:"status"` // 状态
|
||||
Platform string `db:"platform"` // 平台
|
||||
MinCount int64 `db:"min_count"` // 数量
|
||||
Uuid string `db:"uuid"` // 网络id
|
||||
StartTime string `db:"start_time"` // 开始时间
|
||||
RunningTime string `db:"running_time"` // 运行时间
|
||||
Result string `db:"result"` // 运行结果
|
||||
Remark string `db:"remark"` // 备注
|
||||
DeletedAt string `db:"deleted_at"` // 删除时间
|
||||
VmName string `db:"vm_name"` // 虚拟机名称
|
||||
Replicas int64 `db:"replicas"` // 副本数
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -96,13 +94,13 @@ func (m *defaultTaskVmModel) FindOne(ctx context.Context, id int64) (*TaskVm, er
|
|||
|
||||
func (m *defaultTaskVmModel) Insert(ctx context.Context, data *TaskVm) (sql.Result, error) {
|
||||
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, taskVmRowsExpectAutoSet)
|
||||
ret, err := m.conn.ExecCtx(ctx, query, data.TaskId, data.Name, data.AdapterId, data.AdapterName, data.ClusterId, data.ClusterName, data.FlavorRef, data.ImageRef, data.Status, data.Platform, data.Description, data.AvailabilityZone, data.MinCount, data.Uuid, data.StartTime, data.RunningTime, data.Result, data.Remark, data.DeletedAt, data.VmName, data.Replicas)
|
||||
ret, err := m.conn.ExecCtx(ctx, query, data.TaskId, data.Name, data.AdapterId, data.AdapterName, data.ClusterId, data.ClusterName, data.FlavorRef, data.ImageRef, data.Status, data.Platform, data.MinCount, data.Uuid, data.StartTime, data.RunningTime, data.Result, data.Remark, data.DeletedAt, data.VmName, data.Replicas)
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (m *defaultTaskVmModel) Update(ctx context.Context, data *TaskVm) error {
|
||||
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, taskVmRowsWithPlaceHolder)
|
||||
_, err := m.conn.ExecCtx(ctx, query, data.TaskId, data.Name, data.AdapterId, data.AdapterName, data.ClusterId, data.ClusterName, data.FlavorRef, data.ImageRef, data.Status, data.Platform, data.Description, data.AvailabilityZone, data.MinCount, data.Uuid, data.StartTime, data.RunningTime, data.Result, data.Remark, data.DeletedAt, data.VmName, data.Replicas, data.Id)
|
||||
_, err := m.conn.ExecCtx(ctx, query, data.TaskId, data.Name, data.AdapterId, data.AdapterName, data.ClusterId, data.ClusterName, data.FlavorRef, data.ImageRef, data.Status, data.Platform, data.MinCount, data.Uuid, data.StartTime, data.RunningTime, data.Result, data.Remark, data.DeletedAt, data.VmName, data.Replicas, data.Id)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
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.
|
|
@ -0,0 +1,14 @@
|
|||
# filippo.io/edwards25519
|
||||
|
||||
```
|
||||
import "filippo.io/edwards25519"
|
||||
```
|
||||
|
||||
This library implements the edwards25519 elliptic curve, exposing the necessary APIs to build a wide array of higher-level primitives.
|
||||
Read the docs at [pkg.go.dev/filippo.io/edwards25519](https://pkg.go.dev/filippo.io/edwards25519).
|
||||
|
||||
The code is originally derived from Adam Langley's internal implementation in the Go standard library, and includes George Tankersley's [performance improvements](https://golang.org/cl/71950). It was then further developed by Henry de Valence for use in ristretto255, and was finally [merged back into the Go standard library](https://golang.org/cl/276272) as of Go 1.17. It now tracks the upstream codebase and extends it with additional functionality.
|
||||
|
||||
Most users don't need this package, and should instead use `crypto/ed25519` for signatures, `golang.org/x/crypto/curve25519` for Diffie-Hellman, or `github.com/gtank/ristretto255` for prime order group logic. However, for anyone currently using a fork of `crypto/internal/edwards25519`/`crypto/ed25519/internal/edwards25519` or `github.com/agl/edwards25519`, this package should be a safer, faster, and more powerful alternative.
|
||||
|
||||
Since this package is meant to curb proliferation of edwards25519 implementations in the Go ecosystem, it welcomes requests for new APIs or reviewable performance improvements.
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) 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 edwards25519 implements group logic for the twisted Edwards curve
|
||||
//
|
||||
// -x^2 + y^2 = 1 + -(121665/121666)*x^2*y^2
|
||||
//
|
||||
// This is better known as the Edwards curve equivalent to Curve25519, and is
|
||||
// the curve used by the Ed25519 signature scheme.
|
||||
//
|
||||
// Most users don't need this package, and should instead use crypto/ed25519 for
|
||||
// signatures, golang.org/x/crypto/curve25519 for Diffie-Hellman, or
|
||||
// github.com/gtank/ristretto255 for prime order group logic.
|
||||
//
|
||||
// However, developers who do need to interact with low-level edwards25519
|
||||
// operations can use this package, which is an extended version of
|
||||
// crypto/internal/edwards25519 from the standard library repackaged as
|
||||
// an importable module.
|
||||
package edwards25519
|
|
@ -0,0 +1,427 @@
|
|||
// Copyright (c) 2017 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 edwards25519
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"filippo.io/edwards25519/field"
|
||||
)
|
||||
|
||||
// Point types.
|
||||
|
||||
type projP1xP1 struct {
|
||||
X, Y, Z, T field.Element
|
||||
}
|
||||
|
||||
type projP2 struct {
|
||||
X, Y, Z field.Element
|
||||
}
|
||||
|
||||
// Point represents a point on the edwards25519 curve.
|
||||
//
|
||||
// This type works similarly to math/big.Int, and all arguments and receivers
|
||||
// are allowed to alias.
|
||||
//
|
||||
// The zero value is NOT valid, and it may be used only as a receiver.
|
||||
type Point struct {
|
||||
// Make the type not comparable (i.e. used with == or as a map key), as
|
||||
// equivalent points can be represented by different Go values.
|
||||
_ incomparable
|
||||
|
||||
// The point is internally represented in extended coordinates (X, Y, Z, T)
|
||||
// where x = X/Z, y = Y/Z, and xy = T/Z per https://eprint.iacr.org/2008/522.
|
||||
x, y, z, t field.Element
|
||||
}
|
||||
|
||||
type incomparable [0]func()
|
||||
|
||||
func checkInitialized(points ...*Point) {
|
||||
for _, p := range points {
|
||||
if p.x == (field.Element{}) && p.y == (field.Element{}) {
|
||||
panic("edwards25519: use of uninitialized Point")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type projCached struct {
|
||||
YplusX, YminusX, Z, T2d field.Element
|
||||
}
|
||||
|
||||
type affineCached struct {
|
||||
YplusX, YminusX, T2d field.Element
|
||||
}
|
||||
|
||||
// Constructors.
|
||||
|
||||
func (v *projP2) Zero() *projP2 {
|
||||
v.X.Zero()
|
||||
v.Y.One()
|
||||
v.Z.One()
|
||||
return v
|
||||
}
|
||||
|
||||
// identity is the point at infinity.
|
||||
var identity, _ = new(Point).SetBytes([]byte{
|
||||
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
|
||||
|
||||
// NewIdentityPoint returns a new Point set to the identity.
|
||||
func NewIdentityPoint() *Point {
|
||||
return new(Point).Set(identity)
|
||||
}
|
||||
|
||||
// generator is the canonical curve basepoint. See TestGenerator for the
|
||||
// correspondence of this encoding with the values in RFC 8032.
|
||||
var generator, _ = new(Point).SetBytes([]byte{
|
||||
0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
|
||||
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
|
||||
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66,
|
||||
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66})
|
||||
|
||||
// NewGeneratorPoint returns a new Point set to the canonical generator.
|
||||
func NewGeneratorPoint() *Point {
|
||||
return new(Point).Set(generator)
|
||||
}
|
||||
|
||||
func (v *projCached) Zero() *projCached {
|
||||
v.YplusX.One()
|
||||
v.YminusX.One()
|
||||
v.Z.One()
|
||||
v.T2d.Zero()
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *affineCached) Zero() *affineCached {
|
||||
v.YplusX.One()
|
||||
v.YminusX.One()
|
||||
v.T2d.Zero()
|
||||
return v
|
||||
}
|
||||
|
||||
// Assignments.
|
||||
|
||||
// Set sets v = u, and returns v.
|
||||
func (v *Point) Set(u *Point) *Point {
|
||||
*v = *u
|
||||
return v
|
||||
}
|
||||
|
||||
// Encoding.
|
||||
|
||||
// Bytes returns the canonical 32-byte encoding of v, according to RFC 8032,
|
||||
// Section 5.1.2.
|
||||
func (v *Point) Bytes() []byte {
|
||||
// This function is outlined to make the allocations inline in the caller
|
||||
// rather than happen on the heap.
|
||||
var buf [32]byte
|
||||
return v.bytes(&buf)
|
||||
}
|
||||
|
||||
func (v *Point) bytes(buf *[32]byte) []byte {
|
||||
checkInitialized(v)
|
||||
|
||||
var zInv, x, y field.Element
|
||||
zInv.Invert(&v.z) // zInv = 1 / Z
|
||||
x.Multiply(&v.x, &zInv) // x = X / Z
|
||||
y.Multiply(&v.y, &zInv) // y = Y / Z
|
||||
|
||||
out := copyFieldElement(buf, &y)
|
||||
out[31] |= byte(x.IsNegative() << 7)
|
||||
return out
|
||||
}
|
||||
|
||||
var feOne = new(field.Element).One()
|
||||
|
||||
// SetBytes sets v = x, where x is a 32-byte encoding of v. If x does not
|
||||
// represent a valid point on the curve, SetBytes returns nil and an error and
|
||||
// the receiver is unchanged. Otherwise, SetBytes returns v.
|
||||
//
|
||||
// Note that SetBytes accepts all non-canonical encodings of valid points.
|
||||
// That is, it follows decoding rules that match most implementations in
|
||||
// the ecosystem rather than RFC 8032.
|
||||
func (v *Point) SetBytes(x []byte) (*Point, error) {
|
||||
// Specifically, the non-canonical encodings that are accepted are
|
||||
// 1) the ones where the field element is not reduced (see the
|
||||
// (*field.Element).SetBytes docs) and
|
||||
// 2) the ones where the x-coordinate is zero and the sign bit is set.
|
||||
//
|
||||
// Read more at https://hdevalence.ca/blog/2020-10-04-its-25519am,
|
||||
// specifically the "Canonical A, R" section.
|
||||
|
||||
y, err := new(field.Element).SetBytes(x)
|
||||
if err != nil {
|
||||
return nil, errors.New("edwards25519: invalid point encoding length")
|
||||
}
|
||||
|
||||
// -x² + y² = 1 + dx²y²
|
||||
// x² + dx²y² = x²(dy² + 1) = y² - 1
|
||||
// x² = (y² - 1) / (dy² + 1)
|
||||
|
||||
// u = y² - 1
|
||||
y2 := new(field.Element).Square(y)
|
||||
u := new(field.Element).Subtract(y2, feOne)
|
||||
|
||||
// v = dy² + 1
|
||||
vv := new(field.Element).Multiply(y2, d)
|
||||
vv = vv.Add(vv, feOne)
|
||||
|
||||
// x = +√(u/v)
|
||||
xx, wasSquare := new(field.Element).SqrtRatio(u, vv)
|
||||
if wasSquare == 0 {
|
||||
return nil, errors.New("edwards25519: invalid point encoding")
|
||||
}
|
||||
|
||||
// Select the negative square root if the sign bit is set.
|
||||
xxNeg := new(field.Element).Negate(xx)
|
||||
xx = xx.Select(xxNeg, xx, int(x[31]>>7))
|
||||
|
||||
v.x.Set(xx)
|
||||
v.y.Set(y)
|
||||
v.z.One()
|
||||
v.t.Multiply(xx, y) // xy = T / Z
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func copyFieldElement(buf *[32]byte, v *field.Element) []byte {
|
||||
copy(buf[:], v.Bytes())
|
||||
return buf[:]
|
||||
}
|
||||
|
||||
// Conversions.
|
||||
|
||||
func (v *projP2) FromP1xP1(p *projP1xP1) *projP2 {
|
||||
v.X.Multiply(&p.X, &p.T)
|
||||
v.Y.Multiply(&p.Y, &p.Z)
|
||||
v.Z.Multiply(&p.Z, &p.T)
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *projP2) FromP3(p *Point) *projP2 {
|
||||
v.X.Set(&p.x)
|
||||
v.Y.Set(&p.y)
|
||||
v.Z.Set(&p.z)
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *Point) fromP1xP1(p *projP1xP1) *Point {
|
||||
v.x.Multiply(&p.X, &p.T)
|
||||
v.y.Multiply(&p.Y, &p.Z)
|
||||
v.z.Multiply(&p.Z, &p.T)
|
||||
v.t.Multiply(&p.X, &p.Y)
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *Point) fromP2(p *projP2) *Point {
|
||||
v.x.Multiply(&p.X, &p.Z)
|
||||
v.y.Multiply(&p.Y, &p.Z)
|
||||
v.z.Square(&p.Z)
|
||||
v.t.Multiply(&p.X, &p.Y)
|
||||
return v
|
||||
}
|
||||
|
||||
// d is a constant in the curve equation.
|
||||
var d, _ = new(field.Element).SetBytes([]byte{
|
||||
0xa3, 0x78, 0x59, 0x13, 0xca, 0x4d, 0xeb, 0x75,
|
||||
0xab, 0xd8, 0x41, 0x41, 0x4d, 0x0a, 0x70, 0x00,
|
||||
0x98, 0xe8, 0x79, 0x77, 0x79, 0x40, 0xc7, 0x8c,
|
||||
0x73, 0xfe, 0x6f, 0x2b, 0xee, 0x6c, 0x03, 0x52})
|
||||
var d2 = new(field.Element).Add(d, d)
|
||||
|
||||
func (v *projCached) FromP3(p *Point) *projCached {
|
||||
v.YplusX.Add(&p.y, &p.x)
|
||||
v.YminusX.Subtract(&p.y, &p.x)
|
||||
v.Z.Set(&p.z)
|
||||
v.T2d.Multiply(&p.t, d2)
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *affineCached) FromP3(p *Point) *affineCached {
|
||||
v.YplusX.Add(&p.y, &p.x)
|
||||
v.YminusX.Subtract(&p.y, &p.x)
|
||||
v.T2d.Multiply(&p.t, d2)
|
||||
|
||||
var invZ field.Element
|
||||
invZ.Invert(&p.z)
|
||||
v.YplusX.Multiply(&v.YplusX, &invZ)
|
||||
v.YminusX.Multiply(&v.YminusX, &invZ)
|
||||
v.T2d.Multiply(&v.T2d, &invZ)
|
||||
return v
|
||||
}
|
||||
|
||||
// (Re)addition and subtraction.
|
||||
|
||||
// Add sets v = p + q, and returns v.
|
||||
func (v *Point) Add(p, q *Point) *Point {
|
||||
checkInitialized(p, q)
|
||||
qCached := new(projCached).FromP3(q)
|
||||
result := new(projP1xP1).Add(p, qCached)
|
||||
return v.fromP1xP1(result)
|
||||
}
|
||||
|
||||
// Subtract sets v = p - q, and returns v.
|
||||
func (v *Point) Subtract(p, q *Point) *Point {
|
||||
checkInitialized(p, q)
|
||||
qCached := new(projCached).FromP3(q)
|
||||
result := new(projP1xP1).Sub(p, qCached)
|
||||
return v.fromP1xP1(result)
|
||||
}
|
||||
|
||||
func (v *projP1xP1) Add(p *Point, q *projCached) *projP1xP1 {
|
||||
var YplusX, YminusX, PP, MM, TT2d, ZZ2 field.Element
|
||||
|
||||
YplusX.Add(&p.y, &p.x)
|
||||
YminusX.Subtract(&p.y, &p.x)
|
||||
|
||||
PP.Multiply(&YplusX, &q.YplusX)
|
||||
MM.Multiply(&YminusX, &q.YminusX)
|
||||
TT2d.Multiply(&p.t, &q.T2d)
|
||||
ZZ2.Multiply(&p.z, &q.Z)
|
||||
|
||||
ZZ2.Add(&ZZ2, &ZZ2)
|
||||
|
||||
v.X.Subtract(&PP, &MM)
|
||||
v.Y.Add(&PP, &MM)
|
||||
v.Z.Add(&ZZ2, &TT2d)
|
||||
v.T.Subtract(&ZZ2, &TT2d)
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *projP1xP1) Sub(p *Point, q *projCached) *projP1xP1 {
|
||||
var YplusX, YminusX, PP, MM, TT2d, ZZ2 field.Element
|
||||
|
||||
YplusX.Add(&p.y, &p.x)
|
||||
YminusX.Subtract(&p.y, &p.x)
|
||||
|
||||
PP.Multiply(&YplusX, &q.YminusX) // flipped sign
|
||||
MM.Multiply(&YminusX, &q.YplusX) // flipped sign
|
||||
TT2d.Multiply(&p.t, &q.T2d)
|
||||
ZZ2.Multiply(&p.z, &q.Z)
|
||||
|
||||
ZZ2.Add(&ZZ2, &ZZ2)
|
||||
|
||||
v.X.Subtract(&PP, &MM)
|
||||
v.Y.Add(&PP, &MM)
|
||||
v.Z.Subtract(&ZZ2, &TT2d) // flipped sign
|
||||
v.T.Add(&ZZ2, &TT2d) // flipped sign
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *projP1xP1) AddAffine(p *Point, q *affineCached) *projP1xP1 {
|
||||
var YplusX, YminusX, PP, MM, TT2d, Z2 field.Element
|
||||
|
||||
YplusX.Add(&p.y, &p.x)
|
||||
YminusX.Subtract(&p.y, &p.x)
|
||||
|
||||
PP.Multiply(&YplusX, &q.YplusX)
|
||||
MM.Multiply(&YminusX, &q.YminusX)
|
||||
TT2d.Multiply(&p.t, &q.T2d)
|
||||
|
||||
Z2.Add(&p.z, &p.z)
|
||||
|
||||
v.X.Subtract(&PP, &MM)
|
||||
v.Y.Add(&PP, &MM)
|
||||
v.Z.Add(&Z2, &TT2d)
|
||||
v.T.Subtract(&Z2, &TT2d)
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *projP1xP1) SubAffine(p *Point, q *affineCached) *projP1xP1 {
|
||||
var YplusX, YminusX, PP, MM, TT2d, Z2 field.Element
|
||||
|
||||
YplusX.Add(&p.y, &p.x)
|
||||
YminusX.Subtract(&p.y, &p.x)
|
||||
|
||||
PP.Multiply(&YplusX, &q.YminusX) // flipped sign
|
||||
MM.Multiply(&YminusX, &q.YplusX) // flipped sign
|
||||
TT2d.Multiply(&p.t, &q.T2d)
|
||||
|
||||
Z2.Add(&p.z, &p.z)
|
||||
|
||||
v.X.Subtract(&PP, &MM)
|
||||
v.Y.Add(&PP, &MM)
|
||||
v.Z.Subtract(&Z2, &TT2d) // flipped sign
|
||||
v.T.Add(&Z2, &TT2d) // flipped sign
|
||||
return v
|
||||
}
|
||||
|
||||
// Doubling.
|
||||
|
||||
func (v *projP1xP1) Double(p *projP2) *projP1xP1 {
|
||||
var XX, YY, ZZ2, XplusYsq field.Element
|
||||
|
||||
XX.Square(&p.X)
|
||||
YY.Square(&p.Y)
|
||||
ZZ2.Square(&p.Z)
|
||||
ZZ2.Add(&ZZ2, &ZZ2)
|
||||
XplusYsq.Add(&p.X, &p.Y)
|
||||
XplusYsq.Square(&XplusYsq)
|
||||
|
||||
v.Y.Add(&YY, &XX)
|
||||
v.Z.Subtract(&YY, &XX)
|
||||
|
||||
v.X.Subtract(&XplusYsq, &v.Y)
|
||||
v.T.Subtract(&ZZ2, &v.Z)
|
||||
return v
|
||||
}
|
||||
|
||||
// Negation.
|
||||
|
||||
// Negate sets v = -p, and returns v.
|
||||
func (v *Point) Negate(p *Point) *Point {
|
||||
checkInitialized(p)
|
||||
v.x.Negate(&p.x)
|
||||
v.y.Set(&p.y)
|
||||
v.z.Set(&p.z)
|
||||
v.t.Negate(&p.t)
|
||||
return v
|
||||
}
|
||||
|
||||
// Equal returns 1 if v is equivalent to u, and 0 otherwise.
|
||||
func (v *Point) Equal(u *Point) int {
|
||||
checkInitialized(v, u)
|
||||
|
||||
var t1, t2, t3, t4 field.Element
|
||||
t1.Multiply(&v.x, &u.z)
|
||||
t2.Multiply(&u.x, &v.z)
|
||||
t3.Multiply(&v.y, &u.z)
|
||||
t4.Multiply(&u.y, &v.z)
|
||||
|
||||
return t1.Equal(&t2) & t3.Equal(&t4)
|
||||
}
|
||||
|
||||
// Constant-time operations
|
||||
|
||||
// Select sets v to a if cond == 1 and to b if cond == 0.
|
||||
func (v *projCached) Select(a, b *projCached, cond int) *projCached {
|
||||
v.YplusX.Select(&a.YplusX, &b.YplusX, cond)
|
||||
v.YminusX.Select(&a.YminusX, &b.YminusX, cond)
|
||||
v.Z.Select(&a.Z, &b.Z, cond)
|
||||
v.T2d.Select(&a.T2d, &b.T2d, cond)
|
||||
return v
|
||||
}
|
||||
|
||||
// Select sets v to a if cond == 1 and to b if cond == 0.
|
||||
func (v *affineCached) Select(a, b *affineCached, cond int) *affineCached {
|
||||
v.YplusX.Select(&a.YplusX, &b.YplusX, cond)
|
||||
v.YminusX.Select(&a.YminusX, &b.YminusX, cond)
|
||||
v.T2d.Select(&a.T2d, &b.T2d, cond)
|
||||
return v
|
||||
}
|
||||
|
||||
// CondNeg negates v if cond == 1 and leaves it unchanged if cond == 0.
|
||||
func (v *projCached) CondNeg(cond int) *projCached {
|
||||
v.YplusX.Swap(&v.YminusX, cond)
|
||||
v.T2d.Select(new(field.Element).Negate(&v.T2d), &v.T2d, cond)
|
||||
return v
|
||||
}
|
||||
|
||||
// CondNeg negates v if cond == 1 and leaves it unchanged if cond == 0.
|
||||
func (v *affineCached) CondNeg(cond int) *affineCached {
|
||||
v.YplusX.Swap(&v.YminusX, cond)
|
||||
v.T2d.Select(new(field.Element).Negate(&v.T2d), &v.T2d, cond)
|
||||
return v
|
||||
}
|
|
@ -0,0 +1,349 @@
|
|||
// Copyright (c) 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 edwards25519
|
||||
|
||||
// This file contains additional functionality that is not included in the
|
||||
// upstream crypto/internal/edwards25519 package.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"filippo.io/edwards25519/field"
|
||||
)
|
||||
|
||||
// ExtendedCoordinates returns v in extended coordinates (X:Y:Z:T) where
|
||||
// x = X/Z, y = Y/Z, and xy = T/Z as in https://eprint.iacr.org/2008/522.
|
||||
func (v *Point) ExtendedCoordinates() (X, Y, Z, T *field.Element) {
|
||||
// This function is outlined to make the allocations inline in the caller
|
||||
// rather than happen on the heap. Don't change the style without making
|
||||
// sure it doesn't increase the inliner cost.
|
||||
var e [4]field.Element
|
||||
X, Y, Z, T = v.extendedCoordinates(&e)
|
||||
return
|
||||
}
|
||||
|
||||
func (v *Point) extendedCoordinates(e *[4]field.Element) (X, Y, Z, T *field.Element) {
|
||||
checkInitialized(v)
|
||||
X = e[0].Set(&v.x)
|
||||
Y = e[1].Set(&v.y)
|
||||
Z = e[2].Set(&v.z)
|
||||
T = e[3].Set(&v.t)
|
||||
return
|
||||
}
|
||||
|
||||
// SetExtendedCoordinates sets v = (X:Y:Z:T) in extended coordinates where
|
||||
// x = X/Z, y = Y/Z, and xy = T/Z as in https://eprint.iacr.org/2008/522.
|
||||
//
|
||||
// If the coordinates are invalid or don't represent a valid point on the curve,
|
||||
// SetExtendedCoordinates returns nil and an error and the receiver is
|
||||
// unchanged. Otherwise, SetExtendedCoordinates returns v.
|
||||
func (v *Point) SetExtendedCoordinates(X, Y, Z, T *field.Element) (*Point, error) {
|
||||
if !isOnCurve(X, Y, Z, T) {
|
||||
return nil, errors.New("edwards25519: invalid point coordinates")
|
||||
}
|
||||
v.x.Set(X)
|
||||
v.y.Set(Y)
|
||||
v.z.Set(Z)
|
||||
v.t.Set(T)
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func isOnCurve(X, Y, Z, T *field.Element) bool {
|
||||
var lhs, rhs field.Element
|
||||
XX := new(field.Element).Square(X)
|
||||
YY := new(field.Element).Square(Y)
|
||||
ZZ := new(field.Element).Square(Z)
|
||||
TT := new(field.Element).Square(T)
|
||||
// -x² + y² = 1 + dx²y²
|
||||
// -(X/Z)² + (Y/Z)² = 1 + d(T/Z)²
|
||||
// -X² + Y² = Z² + dT²
|
||||
lhs.Subtract(YY, XX)
|
||||
rhs.Multiply(d, TT).Add(&rhs, ZZ)
|
||||
if lhs.Equal(&rhs) != 1 {
|
||||
return false
|
||||
}
|
||||
// xy = T/Z
|
||||
// XY/Z² = T/Z
|
||||
// XY = TZ
|
||||
lhs.Multiply(X, Y)
|
||||
rhs.Multiply(T, Z)
|
||||
return lhs.Equal(&rhs) == 1
|
||||
}
|
||||
|
||||
// BytesMontgomery converts v to a point on the birationally-equivalent
|
||||
// Curve25519 Montgomery curve, and returns its canonical 32 bytes encoding
|
||||
// according to RFC 7748.
|
||||
//
|
||||
// Note that BytesMontgomery only encodes the u-coordinate, so v and -v encode
|
||||
// to the same value. If v is the identity point, BytesMontgomery returns 32
|
||||
// zero bytes, analogously to the X25519 function.
|
||||
//
|
||||
// The lack of an inverse operation (such as SetMontgomeryBytes) is deliberate:
|
||||
// while every valid edwards25519 point has a unique u-coordinate Montgomery
|
||||
// encoding, X25519 accepts inputs on the quadratic twist, which don't correspond
|
||||
// to any edwards25519 point, and every other X25519 input corresponds to two
|
||||
// edwards25519 points.
|
||||
func (v *Point) BytesMontgomery() []byte {
|
||||
// This function is outlined to make the allocations inline in the caller
|
||||
// rather than happen on the heap.
|
||||
var buf [32]byte
|
||||
return v.bytesMontgomery(&buf)
|
||||
}
|
||||
|
||||
func (v *Point) bytesMontgomery(buf *[32]byte) []byte {
|
||||
checkInitialized(v)
|
||||
|
||||
// RFC 7748, Section 4.1 provides the bilinear map to calculate the
|
||||
// Montgomery u-coordinate
|
||||
//
|
||||
// u = (1 + y) / (1 - y)
|
||||
//
|
||||
// where y = Y / Z.
|
||||
|
||||
var y, recip, u field.Element
|
||||
|
||||
y.Multiply(&v.y, y.Invert(&v.z)) // y = Y / Z
|
||||
recip.Invert(recip.Subtract(feOne, &y)) // r = 1/(1 - y)
|
||||
u.Multiply(u.Add(feOne, &y), &recip) // u = (1 + y)*r
|
||||
|
||||
return copyFieldElement(buf, &u)
|
||||
}
|
||||
|
||||
// MultByCofactor sets v = 8 * p, and returns v.
|
||||
func (v *Point) MultByCofactor(p *Point) *Point {
|
||||
checkInitialized(p)
|
||||
result := projP1xP1{}
|
||||
pp := (&projP2{}).FromP3(p)
|
||||
result.Double(pp)
|
||||
pp.FromP1xP1(&result)
|
||||
result.Double(pp)
|
||||
pp.FromP1xP1(&result)
|
||||
result.Double(pp)
|
||||
return v.fromP1xP1(&result)
|
||||
}
|
||||
|
||||
// Given k > 0, set s = s**(2*i).
|
||||
func (s *Scalar) pow2k(k int) {
|
||||
for i := 0; i < k; i++ {
|
||||
s.Multiply(s, s)
|
||||
}
|
||||
}
|
||||
|
||||
// Invert sets s to the inverse of a nonzero scalar v, and returns s.
|
||||
//
|
||||
// If t is zero, Invert returns zero.
|
||||
func (s *Scalar) Invert(t *Scalar) *Scalar {
|
||||
// Uses a hardcoded sliding window of width 4.
|
||||
var table [8]Scalar
|
||||
var tt Scalar
|
||||
tt.Multiply(t, t)
|
||||
table[0] = *t
|
||||
for i := 0; i < 7; i++ {
|
||||
table[i+1].Multiply(&table[i], &tt)
|
||||
}
|
||||
// Now table = [t**1, t**3, t**5, t**7, t**9, t**11, t**13, t**15]
|
||||
// so t**k = t[k/2] for odd k
|
||||
|
||||
// To compute the sliding window digits, use the following Sage script:
|
||||
|
||||
// sage: import itertools
|
||||
// sage: def sliding_window(w,k):
|
||||
// ....: digits = []
|
||||
// ....: while k > 0:
|
||||
// ....: if k % 2 == 1:
|
||||
// ....: kmod = k % (2**w)
|
||||
// ....: digits.append(kmod)
|
||||
// ....: k = k - kmod
|
||||
// ....: else:
|
||||
// ....: digits.append(0)
|
||||
// ....: k = k // 2
|
||||
// ....: return digits
|
||||
|
||||
// Now we can compute s roughly as follows:
|
||||
|
||||
// sage: s = 1
|
||||
// sage: for coeff in reversed(sliding_window(4,l-2)):
|
||||
// ....: s = s*s
|
||||
// ....: if coeff > 0 :
|
||||
// ....: s = s*t**coeff
|
||||
|
||||
// This works on one bit at a time, with many runs of zeros.
|
||||
// The digits can be collapsed into [(count, coeff)] as follows:
|
||||
|
||||
// sage: [(len(list(group)),d) for d,group in itertools.groupby(sliding_window(4,l-2))]
|
||||
|
||||
// Entries of the form (k, 0) turn into pow2k(k)
|
||||
// Entries of the form (1, coeff) turn into a squaring and then a table lookup.
|
||||
// We can fold the squaring into the previous pow2k(k) as pow2k(k+1).
|
||||
|
||||
*s = table[1/2]
|
||||
s.pow2k(127 + 1)
|
||||
s.Multiply(s, &table[1/2])
|
||||
s.pow2k(4 + 1)
|
||||
s.Multiply(s, &table[9/2])
|
||||
s.pow2k(3 + 1)
|
||||
s.Multiply(s, &table[11/2])
|
||||
s.pow2k(3 + 1)
|
||||
s.Multiply(s, &table[13/2])
|
||||
s.pow2k(3 + 1)
|
||||
s.Multiply(s, &table[15/2])
|
||||
s.pow2k(4 + 1)
|
||||
s.Multiply(s, &table[7/2])
|
||||
s.pow2k(4 + 1)
|
||||
s.Multiply(s, &table[15/2])
|
||||
s.pow2k(3 + 1)
|
||||
s.Multiply(s, &table[5/2])
|
||||
s.pow2k(3 + 1)
|
||||
s.Multiply(s, &table[1/2])
|
||||
s.pow2k(4 + 1)
|
||||
s.Multiply(s, &table[15/2])
|
||||
s.pow2k(4 + 1)
|
||||
s.Multiply(s, &table[15/2])
|
||||
s.pow2k(4 + 1)
|
||||
s.Multiply(s, &table[7/2])
|
||||
s.pow2k(3 + 1)
|
||||
s.Multiply(s, &table[3/2])
|
||||
s.pow2k(4 + 1)
|
||||
s.Multiply(s, &table[11/2])
|
||||
s.pow2k(5 + 1)
|
||||
s.Multiply(s, &table[11/2])
|
||||
s.pow2k(9 + 1)
|
||||
s.Multiply(s, &table[9/2])
|
||||
s.pow2k(3 + 1)
|
||||
s.Multiply(s, &table[3/2])
|
||||
s.pow2k(4 + 1)
|
||||
s.Multiply(s, &table[3/2])
|
||||
s.pow2k(4 + 1)
|
||||
s.Multiply(s, &table[3/2])
|
||||
s.pow2k(4 + 1)
|
||||
s.Multiply(s, &table[9/2])
|
||||
s.pow2k(3 + 1)
|
||||
s.Multiply(s, &table[7/2])
|
||||
s.pow2k(3 + 1)
|
||||
s.Multiply(s, &table[3/2])
|
||||
s.pow2k(3 + 1)
|
||||
s.Multiply(s, &table[13/2])
|
||||
s.pow2k(3 + 1)
|
||||
s.Multiply(s, &table[7/2])
|
||||
s.pow2k(4 + 1)
|
||||
s.Multiply(s, &table[9/2])
|
||||
s.pow2k(3 + 1)
|
||||
s.Multiply(s, &table[15/2])
|
||||
s.pow2k(4 + 1)
|
||||
s.Multiply(s, &table[11/2])
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// MultiScalarMult sets v = sum(scalars[i] * points[i]), and returns v.
|
||||
//
|
||||
// Execution time depends only on the lengths of the two slices, which must match.
|
||||
func (v *Point) MultiScalarMult(scalars []*Scalar, points []*Point) *Point {
|
||||
if len(scalars) != len(points) {
|
||||
panic("edwards25519: called MultiScalarMult with different size inputs")
|
||||
}
|
||||
checkInitialized(points...)
|
||||
|
||||
// Proceed as in the single-base case, but share doublings
|
||||
// between each point in the multiscalar equation.
|
||||
|
||||
// Build lookup tables for each point
|
||||
tables := make([]projLookupTable, len(points))
|
||||
for i := range tables {
|
||||
tables[i].FromP3(points[i])
|
||||
}
|
||||
// Compute signed radix-16 digits for each scalar
|
||||
digits := make([][64]int8, len(scalars))
|
||||
for i := range digits {
|
||||
digits[i] = scalars[i].signedRadix16()
|
||||
}
|
||||
|
||||
// Unwrap first loop iteration to save computing 16*identity
|
||||
multiple := &projCached{}
|
||||
tmp1 := &projP1xP1{}
|
||||
tmp2 := &projP2{}
|
||||
// Lookup-and-add the appropriate multiple of each input point
|
||||
for j := range tables {
|
||||
tables[j].SelectInto(multiple, digits[j][63])
|
||||
tmp1.Add(v, multiple) // tmp1 = v + x_(j,63)*Q in P1xP1 coords
|
||||
v.fromP1xP1(tmp1) // update v
|
||||
}
|
||||
tmp2.FromP3(v) // set up tmp2 = v in P2 coords for next iteration
|
||||
for i := 62; i >= 0; i-- {
|
||||
tmp1.Double(tmp2) // tmp1 = 2*(prev) in P1xP1 coords
|
||||
tmp2.FromP1xP1(tmp1) // tmp2 = 2*(prev) in P2 coords
|
||||
tmp1.Double(tmp2) // tmp1 = 4*(prev) in P1xP1 coords
|
||||
tmp2.FromP1xP1(tmp1) // tmp2 = 4*(prev) in P2 coords
|
||||
tmp1.Double(tmp2) // tmp1 = 8*(prev) in P1xP1 coords
|
||||
tmp2.FromP1xP1(tmp1) // tmp2 = 8*(prev) in P2 coords
|
||||
tmp1.Double(tmp2) // tmp1 = 16*(prev) in P1xP1 coords
|
||||
v.fromP1xP1(tmp1) // v = 16*(prev) in P3 coords
|
||||
// Lookup-and-add the appropriate multiple of each input point
|
||||
for j := range tables {
|
||||
tables[j].SelectInto(multiple, digits[j][i])
|
||||
tmp1.Add(v, multiple) // tmp1 = v + x_(j,i)*Q in P1xP1 coords
|
||||
v.fromP1xP1(tmp1) // update v
|
||||
}
|
||||
tmp2.FromP3(v) // set up tmp2 = v in P2 coords for next iteration
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// VarTimeMultiScalarMult sets v = sum(scalars[i] * points[i]), and returns v.
|
||||
//
|
||||
// Execution time depends on the inputs.
|
||||
func (v *Point) VarTimeMultiScalarMult(scalars []*Scalar, points []*Point) *Point {
|
||||
if len(scalars) != len(points) {
|
||||
panic("edwards25519: called VarTimeMultiScalarMult with different size inputs")
|
||||
}
|
||||
checkInitialized(points...)
|
||||
|
||||
// Generalize double-base NAF computation to arbitrary sizes.
|
||||
// Here all the points are dynamic, so we only use the smaller
|
||||
// tables.
|
||||
|
||||
// Build lookup tables for each point
|
||||
tables := make([]nafLookupTable5, len(points))
|
||||
for i := range tables {
|
||||
tables[i].FromP3(points[i])
|
||||
}
|
||||
// Compute a NAF for each scalar
|
||||
nafs := make([][256]int8, len(scalars))
|
||||
for i := range nafs {
|
||||
nafs[i] = scalars[i].nonAdjacentForm(5)
|
||||
}
|
||||
|
||||
multiple := &projCached{}
|
||||
tmp1 := &projP1xP1{}
|
||||
tmp2 := &projP2{}
|
||||
tmp2.Zero()
|
||||
|
||||
// Move from high to low bits, doubling the accumulator
|
||||
// at each iteration and checking whether there is a nonzero
|
||||
// coefficient to look up a multiple of.
|
||||
//
|
||||
// Skip trying to find the first nonzero coefficent, because
|
||||
// searching might be more work than a few extra doublings.
|
||||
for i := 255; i >= 0; i-- {
|
||||
tmp1.Double(tmp2)
|
||||
|
||||
for j := range nafs {
|
||||
if nafs[j][i] > 0 {
|
||||
v.fromP1xP1(tmp1)
|
||||
tables[j].SelectInto(multiple, nafs[j][i])
|
||||
tmp1.Add(v, multiple)
|
||||
} else if nafs[j][i] < 0 {
|
||||
v.fromP1xP1(tmp1)
|
||||
tables[j].SelectInto(multiple, -nafs[j][i])
|
||||
tmp1.Sub(v, multiple)
|
||||
}
|
||||
}
|
||||
|
||||
tmp2.FromP1xP1(tmp1)
|
||||
}
|
||||
|
||||
v.fromP2(tmp2)
|
||||
return v
|
||||
}
|
|
@ -0,0 +1,420 @@
|
|||
// Copyright (c) 2017 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 field implements fast arithmetic modulo 2^255-19.
|
||||
package field
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
// Element represents an element of the field GF(2^255-19). Note that this
|
||||
// is not a cryptographically secure group, and should only be used to interact
|
||||
// with edwards25519.Point coordinates.
|
||||
//
|
||||
// This type works similarly to math/big.Int, and all arguments and receivers
|
||||
// are allowed to alias.
|
||||
//
|
||||
// The zero value is a valid zero element.
|
||||
type Element struct {
|
||||
// An element t represents the integer
|
||||
// t.l0 + t.l1*2^51 + t.l2*2^102 + t.l3*2^153 + t.l4*2^204
|
||||
//
|
||||
// Between operations, all limbs are expected to be lower than 2^52.
|
||||
l0 uint64
|
||||
l1 uint64
|
||||
l2 uint64
|
||||
l3 uint64
|
||||
l4 uint64
|
||||
}
|
||||
|
||||
const maskLow51Bits uint64 = (1 << 51) - 1
|
||||
|
||||
var feZero = &Element{0, 0, 0, 0, 0}
|
||||
|
||||
// Zero sets v = 0, and returns v.
|
||||
func (v *Element) Zero() *Element {
|
||||
*v = *feZero
|
||||
return v
|
||||
}
|
||||
|
||||
var feOne = &Element{1, 0, 0, 0, 0}
|
||||
|
||||
// One sets v = 1, and returns v.
|
||||
func (v *Element) One() *Element {
|
||||
*v = *feOne
|
||||
return v
|
||||
}
|
||||
|
||||
// reduce reduces v modulo 2^255 - 19 and returns it.
|
||||
func (v *Element) reduce() *Element {
|
||||
v.carryPropagate()
|
||||
|
||||
// After the light reduction we now have a field element representation
|
||||
// v < 2^255 + 2^13 * 19, but need v < 2^255 - 19.
|
||||
|
||||
// If v >= 2^255 - 19, then v + 19 >= 2^255, which would overflow 2^255 - 1,
|
||||
// generating a carry. That is, c will be 0 if v < 2^255 - 19, and 1 otherwise.
|
||||
c := (v.l0 + 19) >> 51
|
||||
c = (v.l1 + c) >> 51
|
||||
c = (v.l2 + c) >> 51
|
||||
c = (v.l3 + c) >> 51
|
||||
c = (v.l4 + c) >> 51
|
||||
|
||||
// If v < 2^255 - 19 and c = 0, this will be a no-op. Otherwise, it's
|
||||
// effectively applying the reduction identity to the carry.
|
||||
v.l0 += 19 * c
|
||||
|
||||
v.l1 += v.l0 >> 51
|
||||
v.l0 = v.l0 & maskLow51Bits
|
||||
v.l2 += v.l1 >> 51
|
||||
v.l1 = v.l1 & maskLow51Bits
|
||||
v.l3 += v.l2 >> 51
|
||||
v.l2 = v.l2 & maskLow51Bits
|
||||
v.l4 += v.l3 >> 51
|
||||
v.l3 = v.l3 & maskLow51Bits
|
||||
// no additional carry
|
||||
v.l4 = v.l4 & maskLow51Bits
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// Add sets v = a + b, and returns v.
|
||||
func (v *Element) Add(a, b *Element) *Element {
|
||||
v.l0 = a.l0 + b.l0
|
||||
v.l1 = a.l1 + b.l1
|
||||
v.l2 = a.l2 + b.l2
|
||||
v.l3 = a.l3 + b.l3
|
||||
v.l4 = a.l4 + b.l4
|
||||
// Using the generic implementation here is actually faster than the
|
||||
// assembly. Probably because the body of this function is so simple that
|
||||
// the compiler can figure out better optimizations by inlining the carry
|
||||
// propagation.
|
||||
return v.carryPropagateGeneric()
|
||||
}
|
||||
|
||||
// Subtract sets v = a - b, and returns v.
|
||||
func (v *Element) Subtract(a, b *Element) *Element {
|
||||
// We first add 2 * p, to guarantee the subtraction won't underflow, and
|
||||
// then subtract b (which can be up to 2^255 + 2^13 * 19).
|
||||
v.l0 = (a.l0 + 0xFFFFFFFFFFFDA) - b.l0
|
||||
v.l1 = (a.l1 + 0xFFFFFFFFFFFFE) - b.l1
|
||||
v.l2 = (a.l2 + 0xFFFFFFFFFFFFE) - b.l2
|
||||
v.l3 = (a.l3 + 0xFFFFFFFFFFFFE) - b.l3
|
||||
v.l4 = (a.l4 + 0xFFFFFFFFFFFFE) - b.l4
|
||||
return v.carryPropagate()
|
||||
}
|
||||
|
||||
// Negate sets v = -a, and returns v.
|
||||
func (v *Element) Negate(a *Element) *Element {
|
||||
return v.Subtract(feZero, a)
|
||||
}
|
||||
|
||||
// Invert sets v = 1/z mod p, and returns v.
|
||||
//
|
||||
// If z == 0, Invert returns v = 0.
|
||||
func (v *Element) Invert(z *Element) *Element {
|
||||
// Inversion is implemented as exponentiation with exponent p − 2. It uses the
|
||||
// same sequence of 255 squarings and 11 multiplications as [Curve25519].
|
||||
var z2, z9, z11, z2_5_0, z2_10_0, z2_20_0, z2_50_0, z2_100_0, t Element
|
||||
|
||||
z2.Square(z) // 2
|
||||
t.Square(&z2) // 4
|
||||
t.Square(&t) // 8
|
||||
z9.Multiply(&t, z) // 9
|
||||
z11.Multiply(&z9, &z2) // 11
|
||||
t.Square(&z11) // 22
|
||||
z2_5_0.Multiply(&t, &z9) // 31 = 2^5 - 2^0
|
||||
|
||||
t.Square(&z2_5_0) // 2^6 - 2^1
|
||||
for i := 0; i < 4; i++ {
|
||||
t.Square(&t) // 2^10 - 2^5
|
||||
}
|
||||
z2_10_0.Multiply(&t, &z2_5_0) // 2^10 - 2^0
|
||||
|
||||
t.Square(&z2_10_0) // 2^11 - 2^1
|
||||
for i := 0; i < 9; i++ {
|
||||
t.Square(&t) // 2^20 - 2^10
|
||||
}
|
||||
z2_20_0.Multiply(&t, &z2_10_0) // 2^20 - 2^0
|
||||
|
||||
t.Square(&z2_20_0) // 2^21 - 2^1
|
||||
for i := 0; i < 19; i++ {
|
||||
t.Square(&t) // 2^40 - 2^20
|
||||
}
|
||||
t.Multiply(&t, &z2_20_0) // 2^40 - 2^0
|
||||
|
||||
t.Square(&t) // 2^41 - 2^1
|
||||
for i := 0; i < 9; i++ {
|
||||
t.Square(&t) // 2^50 - 2^10
|
||||
}
|
||||
z2_50_0.Multiply(&t, &z2_10_0) // 2^50 - 2^0
|
||||
|
||||
t.Square(&z2_50_0) // 2^51 - 2^1
|
||||
for i := 0; i < 49; i++ {
|
||||
t.Square(&t) // 2^100 - 2^50
|
||||
}
|
||||
z2_100_0.Multiply(&t, &z2_50_0) // 2^100 - 2^0
|
||||
|
||||
t.Square(&z2_100_0) // 2^101 - 2^1
|
||||
for i := 0; i < 99; i++ {
|
||||
t.Square(&t) // 2^200 - 2^100
|
||||
}
|
||||
t.Multiply(&t, &z2_100_0) // 2^200 - 2^0
|
||||
|
||||
t.Square(&t) // 2^201 - 2^1
|
||||
for i := 0; i < 49; i++ {
|
||||
t.Square(&t) // 2^250 - 2^50
|
||||
}
|
||||
t.Multiply(&t, &z2_50_0) // 2^250 - 2^0
|
||||
|
||||
t.Square(&t) // 2^251 - 2^1
|
||||
t.Square(&t) // 2^252 - 2^2
|
||||
t.Square(&t) // 2^253 - 2^3
|
||||
t.Square(&t) // 2^254 - 2^4
|
||||
t.Square(&t) // 2^255 - 2^5
|
||||
|
||||
return v.Multiply(&t, &z11) // 2^255 - 21
|
||||
}
|
||||
|
||||
// Set sets v = a, and returns v.
|
||||
func (v *Element) Set(a *Element) *Element {
|
||||
*v = *a
|
||||
return v
|
||||
}
|
||||
|
||||
// SetBytes sets v to x, where x is a 32-byte little-endian encoding. If x is
|
||||
// not of the right length, SetBytes returns nil and an error, and the
|
||||
// receiver is unchanged.
|
||||
//
|
||||
// Consistent with RFC 7748, the most significant bit (the high bit of the
|
||||
// last byte) is ignored, and non-canonical values (2^255-19 through 2^255-1)
|
||||
// are accepted. Note that this is laxer than specified by RFC 8032, but
|
||||
// consistent with most Ed25519 implementations.
|
||||
func (v *Element) SetBytes(x []byte) (*Element, error) {
|
||||
if len(x) != 32 {
|
||||
return nil, errors.New("edwards25519: invalid field element input size")
|
||||
}
|
||||
|
||||
// Bits 0:51 (bytes 0:8, bits 0:64, shift 0, mask 51).
|
||||
v.l0 = binary.LittleEndian.Uint64(x[0:8])
|
||||
v.l0 &= maskLow51Bits
|
||||
// Bits 51:102 (bytes 6:14, bits 48:112, shift 3, mask 51).
|
||||
v.l1 = binary.LittleEndian.Uint64(x[6:14]) >> 3
|
||||
v.l1 &= maskLow51Bits
|
||||
// Bits 102:153 (bytes 12:20, bits 96:160, shift 6, mask 51).
|
||||
v.l2 = binary.LittleEndian.Uint64(x[12:20]) >> 6
|
||||
v.l2 &= maskLow51Bits
|
||||
// Bits 153:204 (bytes 19:27, bits 152:216, shift 1, mask 51).
|
||||
v.l3 = binary.LittleEndian.Uint64(x[19:27]) >> 1
|
||||
v.l3 &= maskLow51Bits
|
||||
// Bits 204:255 (bytes 24:32, bits 192:256, shift 12, mask 51).
|
||||
// Note: not bytes 25:33, shift 4, to avoid overread.
|
||||
v.l4 = binary.LittleEndian.Uint64(x[24:32]) >> 12
|
||||
v.l4 &= maskLow51Bits
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Bytes returns the canonical 32-byte little-endian encoding of v.
|
||||
func (v *Element) Bytes() []byte {
|
||||
// This function is outlined to make the allocations inline in the caller
|
||||
// rather than happen on the heap.
|
||||
var out [32]byte
|
||||
return v.bytes(&out)
|
||||
}
|
||||
|
||||
func (v *Element) bytes(out *[32]byte) []byte {
|
||||
t := *v
|
||||
t.reduce()
|
||||
|
||||
var buf [8]byte
|
||||
for i, l := range [5]uint64{t.l0, t.l1, t.l2, t.l3, t.l4} {
|
||||
bitsOffset := i * 51
|
||||
binary.LittleEndian.PutUint64(buf[:], l<<uint(bitsOffset%8))
|
||||
for i, bb := range buf {
|
||||
off := bitsOffset/8 + i
|
||||
if off >= len(out) {
|
||||
break
|
||||
}
|
||||
out[off] |= bb
|
||||
}
|
||||
}
|
||||
|
||||
return out[:]
|
||||
}
|
||||
|
||||
// Equal returns 1 if v and u are equal, and 0 otherwise.
|
||||
func (v *Element) Equal(u *Element) int {
|
||||
sa, sv := u.Bytes(), v.Bytes()
|
||||
return subtle.ConstantTimeCompare(sa, sv)
|
||||
}
|
||||
|
||||
// mask64Bits returns 0xffffffff if cond is 1, and 0 otherwise.
|
||||
func mask64Bits(cond int) uint64 { return ^(uint64(cond) - 1) }
|
||||
|
||||
// Select sets v to a if cond == 1, and to b if cond == 0.
|
||||
func (v *Element) Select(a, b *Element, cond int) *Element {
|
||||
m := mask64Bits(cond)
|
||||
v.l0 = (m & a.l0) | (^m & b.l0)
|
||||
v.l1 = (m & a.l1) | (^m & b.l1)
|
||||
v.l2 = (m & a.l2) | (^m & b.l2)
|
||||
v.l3 = (m & a.l3) | (^m & b.l3)
|
||||
v.l4 = (m & a.l4) | (^m & b.l4)
|
||||
return v
|
||||
}
|
||||
|
||||
// Swap swaps v and u if cond == 1 or leaves them unchanged if cond == 0, and returns v.
|
||||
func (v *Element) Swap(u *Element, cond int) {
|
||||
m := mask64Bits(cond)
|
||||
t := m & (v.l0 ^ u.l0)
|
||||
v.l0 ^= t
|
||||
u.l0 ^= t
|
||||
t = m & (v.l1 ^ u.l1)
|
||||
v.l1 ^= t
|
||||
u.l1 ^= t
|
||||
t = m & (v.l2 ^ u.l2)
|
||||
v.l2 ^= t
|
||||
u.l2 ^= t
|
||||
t = m & (v.l3 ^ u.l3)
|
||||
v.l3 ^= t
|
||||
u.l3 ^= t
|
||||
t = m & (v.l4 ^ u.l4)
|
||||
v.l4 ^= t
|
||||
u.l4 ^= t
|
||||
}
|
||||
|
||||
// IsNegative returns 1 if v is negative, and 0 otherwise.
|
||||
func (v *Element) IsNegative() int {
|
||||
return int(v.Bytes()[0] & 1)
|
||||
}
|
||||
|
||||
// Absolute sets v to |u|, and returns v.
|
||||
func (v *Element) Absolute(u *Element) *Element {
|
||||
return v.Select(new(Element).Negate(u), u, u.IsNegative())
|
||||
}
|
||||
|
||||
// Multiply sets v = x * y, and returns v.
|
||||
func (v *Element) Multiply(x, y *Element) *Element {
|
||||
feMul(v, x, y)
|
||||
return v
|
||||
}
|
||||
|
||||
// Square sets v = x * x, and returns v.
|
||||
func (v *Element) Square(x *Element) *Element {
|
||||
feSquare(v, x)
|
||||
return v
|
||||
}
|
||||
|
||||
// Mult32 sets v = x * y, and returns v.
|
||||
func (v *Element) Mult32(x *Element, y uint32) *Element {
|
||||
x0lo, x0hi := mul51(x.l0, y)
|
||||
x1lo, x1hi := mul51(x.l1, y)
|
||||
x2lo, x2hi := mul51(x.l2, y)
|
||||
x3lo, x3hi := mul51(x.l3, y)
|
||||
x4lo, x4hi := mul51(x.l4, y)
|
||||
v.l0 = x0lo + 19*x4hi // carried over per the reduction identity
|
||||
v.l1 = x1lo + x0hi
|
||||
v.l2 = x2lo + x1hi
|
||||
v.l3 = x3lo + x2hi
|
||||
v.l4 = x4lo + x3hi
|
||||
// The hi portions are going to be only 32 bits, plus any previous excess,
|
||||
// so we can skip the carry propagation.
|
||||
return v
|
||||
}
|
||||
|
||||
// mul51 returns lo + hi * 2⁵¹ = a * b.
|
||||
func mul51(a uint64, b uint32) (lo uint64, hi uint64) {
|
||||
mh, ml := bits.Mul64(a, uint64(b))
|
||||
lo = ml & maskLow51Bits
|
||||
hi = (mh << 13) | (ml >> 51)
|
||||
return
|
||||
}
|
||||
|
||||
// Pow22523 set v = x^((p-5)/8), and returns v. (p-5)/8 is 2^252-3.
|
||||
func (v *Element) Pow22523(x *Element) *Element {
|
||||
var t0, t1, t2 Element
|
||||
|
||||
t0.Square(x) // x^2
|
||||
t1.Square(&t0) // x^4
|
||||
t1.Square(&t1) // x^8
|
||||
t1.Multiply(x, &t1) // x^9
|
||||
t0.Multiply(&t0, &t1) // x^11
|
||||
t0.Square(&t0) // x^22
|
||||
t0.Multiply(&t1, &t0) // x^31
|
||||
t1.Square(&t0) // x^62
|
||||
for i := 1; i < 5; i++ { // x^992
|
||||
t1.Square(&t1)
|
||||
}
|
||||
t0.Multiply(&t1, &t0) // x^1023 -> 1023 = 2^10 - 1
|
||||
t1.Square(&t0) // 2^11 - 2
|
||||
for i := 1; i < 10; i++ { // 2^20 - 2^10
|
||||
t1.Square(&t1)
|
||||
}
|
||||
t1.Multiply(&t1, &t0) // 2^20 - 1
|
||||
t2.Square(&t1) // 2^21 - 2
|
||||
for i := 1; i < 20; i++ { // 2^40 - 2^20
|
||||
t2.Square(&t2)
|
||||
}
|
||||
t1.Multiply(&t2, &t1) // 2^40 - 1
|
||||
t1.Square(&t1) // 2^41 - 2
|
||||
for i := 1; i < 10; i++ { // 2^50 - 2^10
|
||||
t1.Square(&t1)
|
||||
}
|
||||
t0.Multiply(&t1, &t0) // 2^50 - 1
|
||||
t1.Square(&t0) // 2^51 - 2
|
||||
for i := 1; i < 50; i++ { // 2^100 - 2^50
|
||||
t1.Square(&t1)
|
||||
}
|
||||
t1.Multiply(&t1, &t0) // 2^100 - 1
|
||||
t2.Square(&t1) // 2^101 - 2
|
||||
for i := 1; i < 100; i++ { // 2^200 - 2^100
|
||||
t2.Square(&t2)
|
||||
}
|
||||
t1.Multiply(&t2, &t1) // 2^200 - 1
|
||||
t1.Square(&t1) // 2^201 - 2
|
||||
for i := 1; i < 50; i++ { // 2^250 - 2^50
|
||||
t1.Square(&t1)
|
||||
}
|
||||
t0.Multiply(&t1, &t0) // 2^250 - 1
|
||||
t0.Square(&t0) // 2^251 - 2
|
||||
t0.Square(&t0) // 2^252 - 4
|
||||
return v.Multiply(&t0, x) // 2^252 - 3 -> x^(2^252-3)
|
||||
}
|
||||
|
||||
// sqrtM1 is 2^((p-1)/4), which squared is equal to -1 by Euler's Criterion.
|
||||
var sqrtM1 = &Element{1718705420411056, 234908883556509,
|
||||
2233514472574048, 2117202627021982, 765476049583133}
|
||||
|
||||
// SqrtRatio sets r to the non-negative square root of the ratio of u and v.
|
||||
//
|
||||
// If u/v is square, SqrtRatio returns r and 1. If u/v is not square, SqrtRatio
|
||||
// sets r according to Section 4.3 of draft-irtf-cfrg-ristretto255-decaf448-00,
|
||||
// and returns r and 0.
|
||||
func (r *Element) SqrtRatio(u, v *Element) (R *Element, wasSquare int) {
|
||||
t0 := new(Element)
|
||||
|
||||
// r = (u * v3) * (u * v7)^((p-5)/8)
|
||||
v2 := new(Element).Square(v)
|
||||
uv3 := new(Element).Multiply(u, t0.Multiply(v2, v))
|
||||
uv7 := new(Element).Multiply(uv3, t0.Square(v2))
|
||||
rr := new(Element).Multiply(uv3, t0.Pow22523(uv7))
|
||||
|
||||
check := new(Element).Multiply(v, t0.Square(rr)) // check = v * r^2
|
||||
|
||||
uNeg := new(Element).Negate(u)
|
||||
correctSignSqrt := check.Equal(u)
|
||||
flippedSignSqrt := check.Equal(uNeg)
|
||||
flippedSignSqrtI := check.Equal(t0.Multiply(uNeg, sqrtM1))
|
||||
|
||||
rPrime := new(Element).Multiply(rr, sqrtM1) // r_prime = SQRT_M1 * r
|
||||
// r = CT_SELECT(r_prime IF flipped_sign_sqrt | flipped_sign_sqrt_i ELSE r)
|
||||
rr.Select(rPrime, rr, flippedSignSqrt|flippedSignSqrtI)
|
||||
|
||||
r.Absolute(rr) // Choose the nonnegative square root.
|
||||
return r, correctSignSqrt | flippedSignSqrt
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
// Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT.
|
||||
|
||||
//go:build amd64 && gc && !purego
|
||||
// +build amd64,gc,!purego
|
||||
|
||||
package field
|
||||
|
||||
// feMul sets out = a * b. It works like feMulGeneric.
|
||||
//
|
||||
//go:noescape
|
||||
func feMul(out *Element, a *Element, b *Element)
|
||||
|
||||
// feSquare sets out = a * a. It works like feSquareGeneric.
|
||||
//
|
||||
//go:noescape
|
||||
func feSquare(out *Element, a *Element)
|
|
@ -0,0 +1,379 @@
|
|||
// Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT.
|
||||
|
||||
//go:build amd64 && gc && !purego
|
||||
// +build amd64,gc,!purego
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func feMul(out *Element, a *Element, b *Element)
|
||||
TEXT ·feMul(SB), NOSPLIT, $0-24
|
||||
MOVQ a+8(FP), CX
|
||||
MOVQ b+16(FP), BX
|
||||
|
||||
// r0 = a0×b0
|
||||
MOVQ (CX), AX
|
||||
MULQ (BX)
|
||||
MOVQ AX, DI
|
||||
MOVQ DX, SI
|
||||
|
||||
// r0 += 19×a1×b4
|
||||
MOVQ 8(CX), AX
|
||||
IMUL3Q $0x13, AX, AX
|
||||
MULQ 32(BX)
|
||||
ADDQ AX, DI
|
||||
ADCQ DX, SI
|
||||
|
||||
// r0 += 19×a2×b3
|
||||
MOVQ 16(CX), AX
|
||||
IMUL3Q $0x13, AX, AX
|
||||
MULQ 24(BX)
|
||||
ADDQ AX, DI
|
||||
ADCQ DX, SI
|
||||
|
||||
// r0 += 19×a3×b2
|
||||
MOVQ 24(CX), AX
|
||||
IMUL3Q $0x13, AX, AX
|
||||
MULQ 16(BX)
|
||||
ADDQ AX, DI
|
||||
ADCQ DX, SI
|
||||
|
||||
// r0 += 19×a4×b1
|
||||
MOVQ 32(CX), AX
|
||||
IMUL3Q $0x13, AX, AX
|
||||
MULQ 8(BX)
|
||||
ADDQ AX, DI
|
||||
ADCQ DX, SI
|
||||
|
||||
// r1 = a0×b1
|
||||
MOVQ (CX), AX
|
||||
MULQ 8(BX)
|
||||
MOVQ AX, R9
|
||||
MOVQ DX, R8
|
||||
|
||||
// r1 += a1×b0
|
||||
MOVQ 8(CX), AX
|
||||
MULQ (BX)
|
||||
ADDQ AX, R9
|
||||
ADCQ DX, R8
|
||||
|
||||
// r1 += 19×a2×b4
|
||||
MOVQ 16(CX), AX
|
||||
IMUL3Q $0x13, AX, AX
|
||||
MULQ 32(BX)
|
||||
ADDQ AX, R9
|
||||
ADCQ DX, R8
|
||||
|
||||
// r1 += 19×a3×b3
|
||||
MOVQ 24(CX), AX
|
||||
IMUL3Q $0x13, AX, AX
|
||||
MULQ 24(BX)
|
||||
ADDQ AX, R9
|
||||
ADCQ DX, R8
|
||||
|
||||
// r1 += 19×a4×b2
|
||||
MOVQ 32(CX), AX
|
||||
IMUL3Q $0x13, AX, AX
|
||||
MULQ 16(BX)
|
||||
ADDQ AX, R9
|
||||
ADCQ DX, R8
|
||||
|
||||
// r2 = a0×b2
|
||||
MOVQ (CX), AX
|
||||
MULQ 16(BX)
|
||||
MOVQ AX, R11
|
||||
MOVQ DX, R10
|
||||
|
||||
// r2 += a1×b1
|
||||
MOVQ 8(CX), AX
|
||||
MULQ 8(BX)
|
||||
ADDQ AX, R11
|
||||
ADCQ DX, R10
|
||||
|
||||
// r2 += a2×b0
|
||||
MOVQ 16(CX), AX
|
||||
MULQ (BX)
|
||||
ADDQ AX, R11
|
||||
ADCQ DX, R10
|
||||
|
||||
// r2 += 19×a3×b4
|
||||
MOVQ 24(CX), AX
|
||||
IMUL3Q $0x13, AX, AX
|
||||
MULQ 32(BX)
|
||||
ADDQ AX, R11
|
||||
ADCQ DX, R10
|
||||
|
||||
// r2 += 19×a4×b3
|
||||
MOVQ 32(CX), AX
|
||||
IMUL3Q $0x13, AX, AX
|
||||
MULQ 24(BX)
|
||||
ADDQ AX, R11
|
||||
ADCQ DX, R10
|
||||
|
||||
// r3 = a0×b3
|
||||
MOVQ (CX), AX
|
||||
MULQ 24(BX)
|
||||
MOVQ AX, R13
|
||||
MOVQ DX, R12
|
||||
|
||||
// r3 += a1×b2
|
||||
MOVQ 8(CX), AX
|
||||
MULQ 16(BX)
|
||||
ADDQ AX, R13
|
||||
ADCQ DX, R12
|
||||
|
||||
// r3 += a2×b1
|
||||
MOVQ 16(CX), AX
|
||||
MULQ 8(BX)
|
||||
ADDQ AX, R13
|
||||
ADCQ DX, R12
|
||||
|
||||
// r3 += a3×b0
|
||||
MOVQ 24(CX), AX
|
||||
MULQ (BX)
|
||||
ADDQ AX, R13
|
||||
ADCQ DX, R12
|
||||
|
||||
// r3 += 19×a4×b4
|
||||
MOVQ 32(CX), AX
|
||||
IMUL3Q $0x13, AX, AX
|
||||
MULQ 32(BX)
|
||||
ADDQ AX, R13
|
||||
ADCQ DX, R12
|
||||
|
||||
// r4 = a0×b4
|
||||
MOVQ (CX), AX
|
||||
MULQ 32(BX)
|
||||
MOVQ AX, R15
|
||||
MOVQ DX, R14
|
||||
|
||||
// r4 += a1×b3
|
||||
MOVQ 8(CX), AX
|
||||
MULQ 24(BX)
|
||||
ADDQ AX, R15
|
||||
ADCQ DX, R14
|
||||
|
||||
// r4 += a2×b2
|
||||
MOVQ 16(CX), AX
|
||||
MULQ 16(BX)
|
||||
ADDQ AX, R15
|
||||
ADCQ DX, R14
|
||||
|
||||
// r4 += a3×b1
|
||||
MOVQ 24(CX), AX
|
||||
MULQ 8(BX)
|
||||
ADDQ AX, R15
|
||||
ADCQ DX, R14
|
||||
|
||||
// r4 += a4×b0
|
||||
MOVQ 32(CX), AX
|
||||
MULQ (BX)
|
||||
ADDQ AX, R15
|
||||
ADCQ DX, R14
|
||||
|
||||
// First reduction chain
|
||||
MOVQ $0x0007ffffffffffff, AX
|
||||
SHLQ $0x0d, DI, SI
|
||||
SHLQ $0x0d, R9, R8
|
||||
SHLQ $0x0d, R11, R10
|
||||
SHLQ $0x0d, R13, R12
|
||||
SHLQ $0x0d, R15, R14
|
||||
ANDQ AX, DI
|
||||
IMUL3Q $0x13, R14, R14
|
||||
ADDQ R14, DI
|
||||
ANDQ AX, R9
|
||||
ADDQ SI, R9
|
||||
ANDQ AX, R11
|
||||
ADDQ R8, R11
|
||||
ANDQ AX, R13
|
||||
ADDQ R10, R13
|
||||
ANDQ AX, R15
|
||||
ADDQ R12, R15
|
||||
|
||||
// Second reduction chain (carryPropagate)
|
||||
MOVQ DI, SI
|
||||
SHRQ $0x33, SI
|
||||
MOVQ R9, R8
|
||||
SHRQ $0x33, R8
|
||||
MOVQ R11, R10
|
||||
SHRQ $0x33, R10
|
||||
MOVQ R13, R12
|
||||
SHRQ $0x33, R12
|
||||
MOVQ R15, R14
|
||||
SHRQ $0x33, R14
|
||||
ANDQ AX, DI
|
||||
IMUL3Q $0x13, R14, R14
|
||||
ADDQ R14, DI
|
||||
ANDQ AX, R9
|
||||
ADDQ SI, R9
|
||||
ANDQ AX, R11
|
||||
ADDQ R8, R11
|
||||
ANDQ AX, R13
|
||||
ADDQ R10, R13
|
||||
ANDQ AX, R15
|
||||
ADDQ R12, R15
|
||||
|
||||
// Store output
|
||||
MOVQ out+0(FP), AX
|
||||
MOVQ DI, (AX)
|
||||
MOVQ R9, 8(AX)
|
||||
MOVQ R11, 16(AX)
|
||||
MOVQ R13, 24(AX)
|
||||
MOVQ R15, 32(AX)
|
||||
RET
|
||||
|
||||
// func feSquare(out *Element, a *Element)
|
||||
TEXT ·feSquare(SB), NOSPLIT, $0-16
|
||||
MOVQ a+8(FP), CX
|
||||
|
||||
// r0 = l0×l0
|
||||
MOVQ (CX), AX
|
||||
MULQ (CX)
|
||||
MOVQ AX, SI
|
||||
MOVQ DX, BX
|
||||
|
||||
// r0 += 38×l1×l4
|
||||
MOVQ 8(CX), AX
|
||||
IMUL3Q $0x26, AX, AX
|
||||
MULQ 32(CX)
|
||||
ADDQ AX, SI
|
||||
ADCQ DX, BX
|
||||
|
||||
// r0 += 38×l2×l3
|
||||
MOVQ 16(CX), AX
|
||||
IMUL3Q $0x26, AX, AX
|
||||
MULQ 24(CX)
|
||||
ADDQ AX, SI
|
||||
ADCQ DX, BX
|
||||
|
||||
// r1 = 2×l0×l1
|
||||
MOVQ (CX), AX
|
||||
SHLQ $0x01, AX
|
||||
MULQ 8(CX)
|
||||
MOVQ AX, R8
|
||||
MOVQ DX, DI
|
||||
|
||||
// r1 += 38×l2×l4
|
||||
MOVQ 16(CX), AX
|
||||
IMUL3Q $0x26, AX, AX
|
||||
MULQ 32(CX)
|
||||
ADDQ AX, R8
|
||||
ADCQ DX, DI
|
||||
|
||||
// r1 += 19×l3×l3
|
||||
MOVQ 24(CX), AX
|
||||
IMUL3Q $0x13, AX, AX
|
||||
MULQ 24(CX)
|
||||
ADDQ AX, R8
|
||||
ADCQ DX, DI
|
||||
|
||||
// r2 = 2×l0×l2
|
||||
MOVQ (CX), AX
|
||||
SHLQ $0x01, AX
|
||||
MULQ 16(CX)
|
||||
MOVQ AX, R10
|
||||
MOVQ DX, R9
|
||||
|
||||
// r2 += l1×l1
|
||||
MOVQ 8(CX), AX
|
||||
MULQ 8(CX)
|
||||
ADDQ AX, R10
|
||||
ADCQ DX, R9
|
||||
|
||||
// r2 += 38×l3×l4
|
||||
MOVQ 24(CX), AX
|
||||
IMUL3Q $0x26, AX, AX
|
||||
MULQ 32(CX)
|
||||
ADDQ AX, R10
|
||||
ADCQ DX, R9
|
||||
|
||||
// r3 = 2×l0×l3
|
||||
MOVQ (CX), AX
|
||||
SHLQ $0x01, AX
|
||||
MULQ 24(CX)
|
||||
MOVQ AX, R12
|
||||
MOVQ DX, R11
|
||||
|
||||
// r3 += 2×l1×l2
|
||||
MOVQ 8(CX), AX
|
||||
IMUL3Q $0x02, AX, AX
|
||||
MULQ 16(CX)
|
||||
ADDQ AX, R12
|
||||
ADCQ DX, R11
|
||||
|
||||
// r3 += 19×l4×l4
|
||||
MOVQ 32(CX), AX
|
||||
IMUL3Q $0x13, AX, AX
|
||||
MULQ 32(CX)
|
||||
ADDQ AX, R12
|
||||
ADCQ DX, R11
|
||||
|
||||
// r4 = 2×l0×l4
|
||||
MOVQ (CX), AX
|
||||
SHLQ $0x01, AX
|
||||
MULQ 32(CX)
|
||||
MOVQ AX, R14
|
||||
MOVQ DX, R13
|
||||
|
||||
// r4 += 2×l1×l3
|
||||
MOVQ 8(CX), AX
|
||||
IMUL3Q $0x02, AX, AX
|
||||
MULQ 24(CX)
|
||||
ADDQ AX, R14
|
||||
ADCQ DX, R13
|
||||
|
||||
// r4 += l2×l2
|
||||
MOVQ 16(CX), AX
|
||||
MULQ 16(CX)
|
||||
ADDQ AX, R14
|
||||
ADCQ DX, R13
|
||||
|
||||
// First reduction chain
|
||||
MOVQ $0x0007ffffffffffff, AX
|
||||
SHLQ $0x0d, SI, BX
|
||||
SHLQ $0x0d, R8, DI
|
||||
SHLQ $0x0d, R10, R9
|
||||
SHLQ $0x0d, R12, R11
|
||||
SHLQ $0x0d, R14, R13
|
||||
ANDQ AX, SI
|
||||
IMUL3Q $0x13, R13, R13
|
||||
ADDQ R13, SI
|
||||
ANDQ AX, R8
|
||||
ADDQ BX, R8
|
||||
ANDQ AX, R10
|
||||
ADDQ DI, R10
|
||||
ANDQ AX, R12
|
||||
ADDQ R9, R12
|
||||
ANDQ AX, R14
|
||||
ADDQ R11, R14
|
||||
|
||||
// Second reduction chain (carryPropagate)
|
||||
MOVQ SI, BX
|
||||
SHRQ $0x33, BX
|
||||
MOVQ R8, DI
|
||||
SHRQ $0x33, DI
|
||||
MOVQ R10, R9
|
||||
SHRQ $0x33, R9
|
||||
MOVQ R12, R11
|
||||
SHRQ $0x33, R11
|
||||
MOVQ R14, R13
|
||||
SHRQ $0x33, R13
|
||||
ANDQ AX, SI
|
||||
IMUL3Q $0x13, R13, R13
|
||||
ADDQ R13, SI
|
||||
ANDQ AX, R8
|
||||
ADDQ BX, R8
|
||||
ANDQ AX, R10
|
||||
ADDQ DI, R10
|
||||
ANDQ AX, R12
|
||||
ADDQ R9, R12
|
||||
ANDQ AX, R14
|
||||
ADDQ R11, R14
|
||||
|
||||
// Store output
|
||||
MOVQ out+0(FP), AX
|
||||
MOVQ SI, (AX)
|
||||
MOVQ R8, 8(AX)
|
||||
MOVQ R10, 16(AX)
|
||||
MOVQ R12, 24(AX)
|
||||
MOVQ R14, 32(AX)
|
||||
RET
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) 2019 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:build !amd64 || !gc || purego
|
||||
// +build !amd64 !gc purego
|
||||
|
||||
package field
|
||||
|
||||
func feMul(v, x, y *Element) { feMulGeneric(v, x, y) }
|
||||
|
||||
func feSquare(v, x *Element) { feSquareGeneric(v, x) }
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) 2020 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:build arm64 && gc && !purego
|
||||
// +build arm64,gc,!purego
|
||||
|
||||
package field
|
||||
|
||||
//go:noescape
|
||||
func carryPropagate(v *Element)
|
||||
|
||||
func (v *Element) carryPropagate() *Element {
|
||||
carryPropagate(v)
|
||||
return v
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) 2020 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:build arm64 && gc && !purego
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// carryPropagate works exactly like carryPropagateGeneric and uses the
|
||||
// same AND, ADD, and LSR+MADD instructions emitted by the compiler, but
|
||||
// avoids loading R0-R4 twice and uses LDP and STP.
|
||||
//
|
||||
// See https://golang.org/issues/43145 for the main compiler issue.
|
||||
//
|
||||
// func carryPropagate(v *Element)
|
||||
TEXT ·carryPropagate(SB),NOFRAME|NOSPLIT,$0-8
|
||||
MOVD v+0(FP), R20
|
||||
|
||||
LDP 0(R20), (R0, R1)
|
||||
LDP 16(R20), (R2, R3)
|
||||
MOVD 32(R20), R4
|
||||
|
||||
AND $0x7ffffffffffff, R0, R10
|
||||
AND $0x7ffffffffffff, R1, R11
|
||||
AND $0x7ffffffffffff, R2, R12
|
||||
AND $0x7ffffffffffff, R3, R13
|
||||
AND $0x7ffffffffffff, R4, R14
|
||||
|
||||
ADD R0>>51, R11, R11
|
||||
ADD R1>>51, R12, R12
|
||||
ADD R2>>51, R13, R13
|
||||
ADD R3>>51, R14, R14
|
||||
// R4>>51 * 19 + R10 -> R10
|
||||
LSR $51, R4, R21
|
||||
MOVD $19, R22
|
||||
MADD R22, R10, R21, R10
|
||||
|
||||
STP (R10, R11), 0(R20)
|
||||
STP (R12, R13), 16(R20)
|
||||
MOVD R14, 32(R20)
|
||||
|
||||
RET
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) 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.
|
||||
|
||||
//go:build !arm64 || !gc || purego
|
||||
// +build !arm64 !gc purego
|
||||
|
||||
package field
|
||||
|
||||
func (v *Element) carryPropagate() *Element {
|
||||
return v.carryPropagateGeneric()
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) 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 field
|
||||
|
||||
import "errors"
|
||||
|
||||
// This file contains additional functionality that is not included in the
|
||||
// upstream crypto/ed25519/edwards25519/field package.
|
||||
|
||||
// SetWideBytes sets v to x, where x is a 64-byte little-endian encoding, which
|
||||
// is reduced modulo the field order. If x is not of the right length,
|
||||
// SetWideBytes returns nil and an error, and the receiver is unchanged.
|
||||
//
|
||||
// SetWideBytes is not necessary to select a uniformly distributed value, and is
|
||||
// only provided for compatibility: SetBytes can be used instead as the chance
|
||||
// of bias is less than 2⁻²⁵⁰.
|
||||
func (v *Element) SetWideBytes(x []byte) (*Element, error) {
|
||||
if len(x) != 64 {
|
||||
return nil, errors.New("edwards25519: invalid SetWideBytes input size")
|
||||
}
|
||||
|
||||
// Split the 64 bytes into two elements, and extract the most significant
|
||||
// bit of each, which is ignored by SetBytes.
|
||||
lo, _ := new(Element).SetBytes(x[:32])
|
||||
loMSB := uint64(x[31] >> 7)
|
||||
hi, _ := new(Element).SetBytes(x[32:])
|
||||
hiMSB := uint64(x[63] >> 7)
|
||||
|
||||
// The output we want is
|
||||
//
|
||||
// v = lo + loMSB * 2²⁵⁵ + hi * 2²⁵⁶ + hiMSB * 2⁵¹¹
|
||||
//
|
||||
// which applying the reduction identity comes out to
|
||||
//
|
||||
// v = lo + loMSB * 19 + hi * 2 * 19 + hiMSB * 2 * 19²
|
||||
//
|
||||
// l0 will be the sum of a 52 bits value (lo.l0), plus a 5 bits value
|
||||
// (loMSB * 19), a 6 bits value (hi.l0 * 2 * 19), and a 10 bits value
|
||||
// (hiMSB * 2 * 19²), so it fits in a uint64.
|
||||
|
||||
v.l0 = lo.l0 + loMSB*19 + hi.l0*2*19 + hiMSB*2*19*19
|
||||
v.l1 = lo.l1 + hi.l1*2*19
|
||||
v.l2 = lo.l2 + hi.l2*2*19
|
||||
v.l3 = lo.l3 + hi.l3*2*19
|
||||
v.l4 = lo.l4 + hi.l4*2*19
|
||||
|
||||
return v.carryPropagate(), nil
|
||||
}
|
|
@ -0,0 +1,266 @@
|
|||
// Copyright (c) 2017 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 field
|
||||
|
||||
import "math/bits"
|
||||
|
||||
// uint128 holds a 128-bit number as two 64-bit limbs, for use with the
|
||||
// bits.Mul64 and bits.Add64 intrinsics.
|
||||
type uint128 struct {
|
||||
lo, hi uint64
|
||||
}
|
||||
|
||||
// mul64 returns a * b.
|
||||
func mul64(a, b uint64) uint128 {
|
||||
hi, lo := bits.Mul64(a, b)
|
||||
return uint128{lo, hi}
|
||||
}
|
||||
|
||||
// addMul64 returns v + a * b.
|
||||
func addMul64(v uint128, a, b uint64) uint128 {
|
||||
hi, lo := bits.Mul64(a, b)
|
||||
lo, c := bits.Add64(lo, v.lo, 0)
|
||||
hi, _ = bits.Add64(hi, v.hi, c)
|
||||
return uint128{lo, hi}
|
||||
}
|
||||
|
||||
// shiftRightBy51 returns a >> 51. a is assumed to be at most 115 bits.
|
||||
func shiftRightBy51(a uint128) uint64 {
|
||||
return (a.hi << (64 - 51)) | (a.lo >> 51)
|
||||
}
|
||||
|
||||
func feMulGeneric(v, a, b *Element) {
|
||||
a0 := a.l0
|
||||
a1 := a.l1
|
||||
a2 := a.l2
|
||||
a3 := a.l3
|
||||
a4 := a.l4
|
||||
|
||||
b0 := b.l0
|
||||
b1 := b.l1
|
||||
b2 := b.l2
|
||||
b3 := b.l3
|
||||
b4 := b.l4
|
||||
|
||||
// Limb multiplication works like pen-and-paper columnar multiplication, but
|
||||
// with 51-bit limbs instead of digits.
|
||||
//
|
||||
// a4 a3 a2 a1 a0 x
|
||||
// b4 b3 b2 b1 b0 =
|
||||
// ------------------------
|
||||
// a4b0 a3b0 a2b0 a1b0 a0b0 +
|
||||
// a4b1 a3b1 a2b1 a1b1 a0b1 +
|
||||
// a4b2 a3b2 a2b2 a1b2 a0b2 +
|
||||
// a4b3 a3b3 a2b3 a1b3 a0b3 +
|
||||
// a4b4 a3b4 a2b4 a1b4 a0b4 =
|
||||
// ----------------------------------------------
|
||||
// r8 r7 r6 r5 r4 r3 r2 r1 r0
|
||||
//
|
||||
// We can then use the reduction identity (a * 2²⁵⁵ + b = a * 19 + b) to
|
||||
// reduce the limbs that would overflow 255 bits. r5 * 2²⁵⁵ becomes 19 * r5,
|
||||
// r6 * 2³⁰⁶ becomes 19 * r6 * 2⁵¹, etc.
|
||||
//
|
||||
// Reduction can be carried out simultaneously to multiplication. For
|
||||
// example, we do not compute r5: whenever the result of a multiplication
|
||||
// belongs to r5, like a1b4, we multiply it by 19 and add the result to r0.
|
||||
//
|
||||
// a4b0 a3b0 a2b0 a1b0 a0b0 +
|
||||
// a3b1 a2b1 a1b1 a0b1 19×a4b1 +
|
||||
// a2b2 a1b2 a0b2 19×a4b2 19×a3b2 +
|
||||
// a1b3 a0b3 19×a4b3 19×a3b3 19×a2b3 +
|
||||
// a0b4 19×a4b4 19×a3b4 19×a2b4 19×a1b4 =
|
||||
// --------------------------------------
|
||||
// r4 r3 r2 r1 r0
|
||||
//
|
||||
// Finally we add up the columns into wide, overlapping limbs.
|
||||
|
||||
a1_19 := a1 * 19
|
||||
a2_19 := a2 * 19
|
||||
a3_19 := a3 * 19
|
||||
a4_19 := a4 * 19
|
||||
|
||||
// r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1)
|
||||
r0 := mul64(a0, b0)
|
||||
r0 = addMul64(r0, a1_19, b4)
|
||||
r0 = addMul64(r0, a2_19, b3)
|
||||
r0 = addMul64(r0, a3_19, b2)
|
||||
r0 = addMul64(r0, a4_19, b1)
|
||||
|
||||
// r1 = a0×b1 + a1×b0 + 19×(a2×b4 + a3×b3 + a4×b2)
|
||||
r1 := mul64(a0, b1)
|
||||
r1 = addMul64(r1, a1, b0)
|
||||
r1 = addMul64(r1, a2_19, b4)
|
||||
r1 = addMul64(r1, a3_19, b3)
|
||||
r1 = addMul64(r1, a4_19, b2)
|
||||
|
||||
// r2 = a0×b2 + a1×b1 + a2×b0 + 19×(a3×b4 + a4×b3)
|
||||
r2 := mul64(a0, b2)
|
||||
r2 = addMul64(r2, a1, b1)
|
||||
r2 = addMul64(r2, a2, b0)
|
||||
r2 = addMul64(r2, a3_19, b4)
|
||||
r2 = addMul64(r2, a4_19, b3)
|
||||
|
||||
// r3 = a0×b3 + a1×b2 + a2×b1 + a3×b0 + 19×a4×b4
|
||||
r3 := mul64(a0, b3)
|
||||
r3 = addMul64(r3, a1, b2)
|
||||
r3 = addMul64(r3, a2, b1)
|
||||
r3 = addMul64(r3, a3, b0)
|
||||
r3 = addMul64(r3, a4_19, b4)
|
||||
|
||||
// r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0
|
||||
r4 := mul64(a0, b4)
|
||||
r4 = addMul64(r4, a1, b3)
|
||||
r4 = addMul64(r4, a2, b2)
|
||||
r4 = addMul64(r4, a3, b1)
|
||||
r4 = addMul64(r4, a4, b0)
|
||||
|
||||
// After the multiplication, we need to reduce (carry) the five coefficients
|
||||
// to obtain a result with limbs that are at most slightly larger than 2⁵¹,
|
||||
// to respect the Element invariant.
|
||||
//
|
||||
// Overall, the reduction works the same as carryPropagate, except with
|
||||
// wider inputs: we take the carry for each coefficient by shifting it right
|
||||
// by 51, and add it to the limb above it. The top carry is multiplied by 19
|
||||
// according to the reduction identity and added to the lowest limb.
|
||||
//
|
||||
// The largest coefficient (r0) will be at most 111 bits, which guarantees
|
||||
// that all carries are at most 111 - 51 = 60 bits, which fits in a uint64.
|
||||
//
|
||||
// r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1)
|
||||
// r0 < 2⁵²×2⁵² + 19×(2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵²)
|
||||
// r0 < (1 + 19 × 4) × 2⁵² × 2⁵²
|
||||
// r0 < 2⁷ × 2⁵² × 2⁵²
|
||||
// r0 < 2¹¹¹
|
||||
//
|
||||
// Moreover, the top coefficient (r4) is at most 107 bits, so c4 is at most
|
||||
// 56 bits, and c4 * 19 is at most 61 bits, which again fits in a uint64 and
|
||||
// allows us to easily apply the reduction identity.
|
||||
//
|
||||
// r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0
|
||||
// r4 < 5 × 2⁵² × 2⁵²
|
||||
// r4 < 2¹⁰⁷
|
||||
//
|
||||
|
||||
c0 := shiftRightBy51(r0)
|
||||
c1 := shiftRightBy51(r1)
|
||||
c2 := shiftRightBy51(r2)
|
||||
c3 := shiftRightBy51(r3)
|
||||
c4 := shiftRightBy51(r4)
|
||||
|
||||
rr0 := r0.lo&maskLow51Bits + c4*19
|
||||
rr1 := r1.lo&maskLow51Bits + c0
|
||||
rr2 := r2.lo&maskLow51Bits + c1
|
||||
rr3 := r3.lo&maskLow51Bits + c2
|
||||
rr4 := r4.lo&maskLow51Bits + c3
|
||||
|
||||
// Now all coefficients fit into 64-bit registers but are still too large to
|
||||
// be passed around as an Element. We therefore do one last carry chain,
|
||||
// where the carries will be small enough to fit in the wiggle room above 2⁵¹.
|
||||
*v = Element{rr0, rr1, rr2, rr3, rr4}
|
||||
v.carryPropagate()
|
||||
}
|
||||
|
||||
func feSquareGeneric(v, a *Element) {
|
||||
l0 := a.l0
|
||||
l1 := a.l1
|
||||
l2 := a.l2
|
||||
l3 := a.l3
|
||||
l4 := a.l4
|
||||
|
||||
// Squaring works precisely like multiplication above, but thanks to its
|
||||
// symmetry we get to group a few terms together.
|
||||
//
|
||||
// l4 l3 l2 l1 l0 x
|
||||
// l4 l3 l2 l1 l0 =
|
||||
// ------------------------
|
||||
// l4l0 l3l0 l2l0 l1l0 l0l0 +
|
||||
// l4l1 l3l1 l2l1 l1l1 l0l1 +
|
||||
// l4l2 l3l2 l2l2 l1l2 l0l2 +
|
||||
// l4l3 l3l3 l2l3 l1l3 l0l3 +
|
||||
// l4l4 l3l4 l2l4 l1l4 l0l4 =
|
||||
// ----------------------------------------------
|
||||
// r8 r7 r6 r5 r4 r3 r2 r1 r0
|
||||
//
|
||||
// l4l0 l3l0 l2l0 l1l0 l0l0 +
|
||||
// l3l1 l2l1 l1l1 l0l1 19×l4l1 +
|
||||
// l2l2 l1l2 l0l2 19×l4l2 19×l3l2 +
|
||||
// l1l3 l0l3 19×l4l3 19×l3l3 19×l2l3 +
|
||||
// l0l4 19×l4l4 19×l3l4 19×l2l4 19×l1l4 =
|
||||
// --------------------------------------
|
||||
// r4 r3 r2 r1 r0
|
||||
//
|
||||
// With precomputed 2×, 19×, and 2×19× terms, we can compute each limb with
|
||||
// only three Mul64 and four Add64, instead of five and eight.
|
||||
|
||||
l0_2 := l0 * 2
|
||||
l1_2 := l1 * 2
|
||||
|
||||
l1_38 := l1 * 38
|
||||
l2_38 := l2 * 38
|
||||
l3_38 := l3 * 38
|
||||
|
||||
l3_19 := l3 * 19
|
||||
l4_19 := l4 * 19
|
||||
|
||||
// r0 = l0×l0 + 19×(l1×l4 + l2×l3 + l3×l2 + l4×l1) = l0×l0 + 19×2×(l1×l4 + l2×l3)
|
||||
r0 := mul64(l0, l0)
|
||||
r0 = addMul64(r0, l1_38, l4)
|
||||
r0 = addMul64(r0, l2_38, l3)
|
||||
|
||||
// r1 = l0×l1 + l1×l0 + 19×(l2×l4 + l3×l3 + l4×l2) = 2×l0×l1 + 19×2×l2×l4 + 19×l3×l3
|
||||
r1 := mul64(l0_2, l1)
|
||||
r1 = addMul64(r1, l2_38, l4)
|
||||
r1 = addMul64(r1, l3_19, l3)
|
||||
|
||||
// r2 = l0×l2 + l1×l1 + l2×l0 + 19×(l3×l4 + l4×l3) = 2×l0×l2 + l1×l1 + 19×2×l3×l4
|
||||
r2 := mul64(l0_2, l2)
|
||||
r2 = addMul64(r2, l1, l1)
|
||||
r2 = addMul64(r2, l3_38, l4)
|
||||
|
||||
// r3 = l0×l3 + l1×l2 + l2×l1 + l3×l0 + 19×l4×l4 = 2×l0×l3 + 2×l1×l2 + 19×l4×l4
|
||||
r3 := mul64(l0_2, l3)
|
||||
r3 = addMul64(r3, l1_2, l2)
|
||||
r3 = addMul64(r3, l4_19, l4)
|
||||
|
||||
// r4 = l0×l4 + l1×l3 + l2×l2 + l3×l1 + l4×l0 = 2×l0×l4 + 2×l1×l3 + l2×l2
|
||||
r4 := mul64(l0_2, l4)
|
||||
r4 = addMul64(r4, l1_2, l3)
|
||||
r4 = addMul64(r4, l2, l2)
|
||||
|
||||
c0 := shiftRightBy51(r0)
|
||||
c1 := shiftRightBy51(r1)
|
||||
c2 := shiftRightBy51(r2)
|
||||
c3 := shiftRightBy51(r3)
|
||||
c4 := shiftRightBy51(r4)
|
||||
|
||||
rr0 := r0.lo&maskLow51Bits + c4*19
|
||||
rr1 := r1.lo&maskLow51Bits + c0
|
||||
rr2 := r2.lo&maskLow51Bits + c1
|
||||
rr3 := r3.lo&maskLow51Bits + c2
|
||||
rr4 := r4.lo&maskLow51Bits + c3
|
||||
|
||||
*v = Element{rr0, rr1, rr2, rr3, rr4}
|
||||
v.carryPropagate()
|
||||
}
|
||||
|
||||
// carryPropagateGeneric brings the limbs below 52 bits by applying the reduction
|
||||
// identity (a * 2²⁵⁵ + b = a * 19 + b) to the l4 carry.
|
||||
func (v *Element) carryPropagateGeneric() *Element {
|
||||
c0 := v.l0 >> 51
|
||||
c1 := v.l1 >> 51
|
||||
c2 := v.l2 >> 51
|
||||
c3 := v.l3 >> 51
|
||||
c4 := v.l4 >> 51
|
||||
|
||||
// c4 is at most 64 - 51 = 13 bits, so c4*19 is at most 18 bits, and
|
||||
// the final l0 will be at most 52 bits. Similarly for the rest.
|
||||
v.l0 = v.l0&maskLow51Bits + c4*19
|
||||
v.l1 = v.l1&maskLow51Bits + c0
|
||||
v.l2 = v.l2&maskLow51Bits + c1
|
||||
v.l3 = v.l3&maskLow51Bits + c2
|
||||
v.l4 = v.l4&maskLow51Bits + c3
|
||||
|
||||
return v
|
||||
}
|
|
@ -0,0 +1,343 @@
|
|||
// Copyright (c) 2016 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 edwards25519
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// A Scalar is an integer modulo
|
||||
//
|
||||
// l = 2^252 + 27742317777372353535851937790883648493
|
||||
//
|
||||
// which is the prime order of the edwards25519 group.
|
||||
//
|
||||
// This type works similarly to math/big.Int, and all arguments and
|
||||
// receivers are allowed to alias.
|
||||
//
|
||||
// The zero value is a valid zero element.
|
||||
type Scalar struct {
|
||||
// s is the scalar in the Montgomery domain, in the format of the
|
||||
// fiat-crypto implementation.
|
||||
s fiatScalarMontgomeryDomainFieldElement
|
||||
}
|
||||
|
||||
// The field implementation in scalar_fiat.go is generated by the fiat-crypto
|
||||
// project (https://github.com/mit-plv/fiat-crypto) at version v0.0.9 (23d2dbc)
|
||||
// from a formally verified model.
|
||||
//
|
||||
// fiat-crypto code comes under the following license.
|
||||
//
|
||||
// Copyright (c) 2015-2020 The fiat-crypto 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:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY the fiat-crypto authors "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 Berkeley Software Design,
|
||||
// Inc. 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.
|
||||
//
|
||||
|
||||
// NewScalar returns a new zero Scalar.
|
||||
func NewScalar() *Scalar {
|
||||
return &Scalar{}
|
||||
}
|
||||
|
||||
// MultiplyAdd sets s = x * y + z mod l, and returns s. It is equivalent to
|
||||
// using Multiply and then Add.
|
||||
func (s *Scalar) MultiplyAdd(x, y, z *Scalar) *Scalar {
|
||||
// Make a copy of z in case it aliases s.
|
||||
zCopy := new(Scalar).Set(z)
|
||||
return s.Multiply(x, y).Add(s, zCopy)
|
||||
}
|
||||
|
||||
// Add sets s = x + y mod l, and returns s.
|
||||
func (s *Scalar) Add(x, y *Scalar) *Scalar {
|
||||
// s = 1 * x + y mod l
|
||||
fiatScalarAdd(&s.s, &x.s, &y.s)
|
||||
return s
|
||||
}
|
||||
|
||||
// Subtract sets s = x - y mod l, and returns s.
|
||||
func (s *Scalar) Subtract(x, y *Scalar) *Scalar {
|
||||
// s = -1 * y + x mod l
|
||||
fiatScalarSub(&s.s, &x.s, &y.s)
|
||||
return s
|
||||
}
|
||||
|
||||
// Negate sets s = -x mod l, and returns s.
|
||||
func (s *Scalar) Negate(x *Scalar) *Scalar {
|
||||
// s = -1 * x + 0 mod l
|
||||
fiatScalarOpp(&s.s, &x.s)
|
||||
return s
|
||||
}
|
||||
|
||||
// Multiply sets s = x * y mod l, and returns s.
|
||||
func (s *Scalar) Multiply(x, y *Scalar) *Scalar {
|
||||
// s = x * y + 0 mod l
|
||||
fiatScalarMul(&s.s, &x.s, &y.s)
|
||||
return s
|
||||
}
|
||||
|
||||
// Set sets s = x, and returns s.
|
||||
func (s *Scalar) Set(x *Scalar) *Scalar {
|
||||
*s = *x
|
||||
return s
|
||||
}
|
||||
|
||||
// SetUniformBytes sets s = x mod l, where x is a 64-byte little-endian integer.
|
||||
// If x is not of the right length, SetUniformBytes returns nil and an error,
|
||||
// and the receiver is unchanged.
|
||||
//
|
||||
// SetUniformBytes can be used to set s to a uniformly distributed value given
|
||||
// 64 uniformly distributed random bytes.
|
||||
func (s *Scalar) SetUniformBytes(x []byte) (*Scalar, error) {
|
||||
if len(x) != 64 {
|
||||
return nil, errors.New("edwards25519: invalid SetUniformBytes input length")
|
||||
}
|
||||
|
||||
// We have a value x of 512 bits, but our fiatScalarFromBytes function
|
||||
// expects an input lower than l, which is a little over 252 bits.
|
||||
//
|
||||
// Instead of writing a reduction function that operates on wider inputs, we
|
||||
// can interpret x as the sum of three shorter values a, b, and c.
|
||||
//
|
||||
// x = a + b * 2^168 + c * 2^336 mod l
|
||||
//
|
||||
// We then precompute 2^168 and 2^336 modulo l, and perform the reduction
|
||||
// with two multiplications and two additions.
|
||||
|
||||
s.setShortBytes(x[:21])
|
||||
t := new(Scalar).setShortBytes(x[21:42])
|
||||
s.Add(s, t.Multiply(t, scalarTwo168))
|
||||
t.setShortBytes(x[42:])
|
||||
s.Add(s, t.Multiply(t, scalarTwo336))
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// scalarTwo168 and scalarTwo336 are 2^168 and 2^336 modulo l, encoded as a
|
||||
// fiatScalarMontgomeryDomainFieldElement, which is a little-endian 4-limb value
|
||||
// in the 2^256 Montgomery domain.
|
||||
var scalarTwo168 = &Scalar{s: [4]uint64{0x5b8ab432eac74798, 0x38afddd6de59d5d7,
|
||||
0xa2c131b399411b7c, 0x6329a7ed9ce5a30}}
|
||||
var scalarTwo336 = &Scalar{s: [4]uint64{0xbd3d108e2b35ecc5, 0x5c3a3718bdf9c90b,
|
||||
0x63aa97a331b4f2ee, 0x3d217f5be65cb5c}}
|
||||
|
||||
// setShortBytes sets s = x mod l, where x is a little-endian integer shorter
|
||||
// than 32 bytes.
|
||||
func (s *Scalar) setShortBytes(x []byte) *Scalar {
|
||||
if len(x) >= 32 {
|
||||
panic("edwards25519: internal error: setShortBytes called with a long string")
|
||||
}
|
||||
var buf [32]byte
|
||||
copy(buf[:], x)
|
||||
fiatScalarFromBytes((*[4]uint64)(&s.s), &buf)
|
||||
fiatScalarToMontgomery(&s.s, (*fiatScalarNonMontgomeryDomainFieldElement)(&s.s))
|
||||
return s
|
||||
}
|
||||
|
||||
// SetCanonicalBytes sets s = x, where x is a 32-byte little-endian encoding of
|
||||
// s, and returns s. If x is not a canonical encoding of s, SetCanonicalBytes
|
||||
// returns nil and an error, and the receiver is unchanged.
|
||||
func (s *Scalar) SetCanonicalBytes(x []byte) (*Scalar, error) {
|
||||
if len(x) != 32 {
|
||||
return nil, errors.New("invalid scalar length")
|
||||
}
|
||||
if !isReduced(x) {
|
||||
return nil, errors.New("invalid scalar encoding")
|
||||
}
|
||||
|
||||
fiatScalarFromBytes((*[4]uint64)(&s.s), (*[32]byte)(x))
|
||||
fiatScalarToMontgomery(&s.s, (*fiatScalarNonMontgomeryDomainFieldElement)(&s.s))
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// scalarMinusOneBytes is l - 1 in little endian.
|
||||
var scalarMinusOneBytes = [32]byte{236, 211, 245, 92, 26, 99, 18, 88, 214, 156, 247, 162, 222, 249, 222, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16}
|
||||
|
||||
// isReduced returns whether the given scalar in 32-byte little endian encoded
|
||||
// form is reduced modulo l.
|
||||
func isReduced(s []byte) bool {
|
||||
if len(s) != 32 {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := len(s) - 1; i >= 0; i-- {
|
||||
switch {
|
||||
case s[i] > scalarMinusOneBytes[i]:
|
||||
return false
|
||||
case s[i] < scalarMinusOneBytes[i]:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SetBytesWithClamping applies the buffer pruning described in RFC 8032,
|
||||
// Section 5.1.5 (also known as clamping) and sets s to the result. The input
|
||||
// must be 32 bytes, and it is not modified. If x is not of the right length,
|
||||
// SetBytesWithClamping returns nil and an error, and the receiver is unchanged.
|
||||
//
|
||||
// Note that since Scalar values are always reduced modulo the prime order of
|
||||
// the curve, the resulting value will not preserve any of the cofactor-clearing
|
||||
// properties that clamping is meant to provide. It will however work as
|
||||
// expected as long as it is applied to points on the prime order subgroup, like
|
||||
// in Ed25519. In fact, it is lost to history why RFC 8032 adopted the
|
||||
// irrelevant RFC 7748 clamping, but it is now required for compatibility.
|
||||
func (s *Scalar) SetBytesWithClamping(x []byte) (*Scalar, error) {
|
||||
// The description above omits the purpose of the high bits of the clamping
|
||||
// for brevity, but those are also lost to reductions, and are also
|
||||
// irrelevant to edwards25519 as they protect against a specific
|
||||
// implementation bug that was once observed in a generic Montgomery ladder.
|
||||
if len(x) != 32 {
|
||||
return nil, errors.New("edwards25519: invalid SetBytesWithClamping input length")
|
||||
}
|
||||
|
||||
// We need to use the wide reduction from SetUniformBytes, since clamping
|
||||
// sets the 2^254 bit, making the value higher than the order.
|
||||
var wideBytes [64]byte
|
||||
copy(wideBytes[:], x[:])
|
||||
wideBytes[0] &= 248
|
||||
wideBytes[31] &= 63
|
||||
wideBytes[31] |= 64
|
||||
return s.SetUniformBytes(wideBytes[:])
|
||||
}
|
||||
|
||||
// Bytes returns the canonical 32-byte little-endian encoding of s.
|
||||
func (s *Scalar) Bytes() []byte {
|
||||
// This function is outlined to make the allocations inline in the caller
|
||||
// rather than happen on the heap.
|
||||
var encoded [32]byte
|
||||
return s.bytes(&encoded)
|
||||
}
|
||||
|
||||
func (s *Scalar) bytes(out *[32]byte) []byte {
|
||||
var ss fiatScalarNonMontgomeryDomainFieldElement
|
||||
fiatScalarFromMontgomery(&ss, &s.s)
|
||||
fiatScalarToBytes(out, (*[4]uint64)(&ss))
|
||||
return out[:]
|
||||
}
|
||||
|
||||
// Equal returns 1 if s and t are equal, and 0 otherwise.
|
||||
func (s *Scalar) Equal(t *Scalar) int {
|
||||
var diff fiatScalarMontgomeryDomainFieldElement
|
||||
fiatScalarSub(&diff, &s.s, &t.s)
|
||||
var nonzero uint64
|
||||
fiatScalarNonzero(&nonzero, (*[4]uint64)(&diff))
|
||||
nonzero |= nonzero >> 32
|
||||
nonzero |= nonzero >> 16
|
||||
nonzero |= nonzero >> 8
|
||||
nonzero |= nonzero >> 4
|
||||
nonzero |= nonzero >> 2
|
||||
nonzero |= nonzero >> 1
|
||||
return int(^nonzero) & 1
|
||||
}
|
||||
|
||||
// nonAdjacentForm computes a width-w non-adjacent form for this scalar.
|
||||
//
|
||||
// w must be between 2 and 8, or nonAdjacentForm will panic.
|
||||
func (s *Scalar) nonAdjacentForm(w uint) [256]int8 {
|
||||
// This implementation is adapted from the one
|
||||
// in curve25519-dalek and is documented there:
|
||||
// https://github.com/dalek-cryptography/curve25519-dalek/blob/f630041af28e9a405255f98a8a93adca18e4315b/src/scalar.rs#L800-L871
|
||||
b := s.Bytes()
|
||||
if b[31] > 127 {
|
||||
panic("scalar has high bit set illegally")
|
||||
}
|
||||
if w < 2 {
|
||||
panic("w must be at least 2 by the definition of NAF")
|
||||
} else if w > 8 {
|
||||
panic("NAF digits must fit in int8")
|
||||
}
|
||||
|
||||
var naf [256]int8
|
||||
var digits [5]uint64
|
||||
|
||||
for i := 0; i < 4; i++ {
|
||||
digits[i] = binary.LittleEndian.Uint64(b[i*8:])
|
||||
}
|
||||
|
||||
width := uint64(1 << w)
|
||||
windowMask := uint64(width - 1)
|
||||
|
||||
pos := uint(0)
|
||||
carry := uint64(0)
|
||||
for pos < 256 {
|
||||
indexU64 := pos / 64
|
||||
indexBit := pos % 64
|
||||
var bitBuf uint64
|
||||
if indexBit < 64-w {
|
||||
// This window's bits are contained in a single u64
|
||||
bitBuf = digits[indexU64] >> indexBit
|
||||
} else {
|
||||
// Combine the current 64 bits with bits from the next 64
|
||||
bitBuf = (digits[indexU64] >> indexBit) | (digits[1+indexU64] << (64 - indexBit))
|
||||
}
|
||||
|
||||
// Add carry into the current window
|
||||
window := carry + (bitBuf & windowMask)
|
||||
|
||||
if window&1 == 0 {
|
||||
// If the window value is even, preserve the carry and continue.
|
||||
// Why is the carry preserved?
|
||||
// If carry == 0 and window & 1 == 0,
|
||||
// then the next carry should be 0
|
||||
// If carry == 1 and window & 1 == 0,
|
||||
// then bit_buf & 1 == 1 so the next carry should be 1
|
||||
pos += 1
|
||||
continue
|
||||
}
|
||||
|
||||
if window < width/2 {
|
||||
carry = 0
|
||||
naf[pos] = int8(window)
|
||||
} else {
|
||||
carry = 1
|
||||
naf[pos] = int8(window) - int8(width)
|
||||
}
|
||||
|
||||
pos += w
|
||||
}
|
||||
return naf
|
||||
}
|
||||
|
||||
func (s *Scalar) signedRadix16() [64]int8 {
|
||||
b := s.Bytes()
|
||||
if b[31] > 127 {
|
||||
panic("scalar has high bit set illegally")
|
||||
}
|
||||
|
||||
var digits [64]int8
|
||||
|
||||
// Compute unsigned radix-16 digits:
|
||||
for i := 0; i < 32; i++ {
|
||||
digits[2*i] = int8(b[i] & 15)
|
||||
digits[2*i+1] = int8((b[i] >> 4) & 15)
|
||||
}
|
||||
|
||||
// Recenter coefficients:
|
||||
for i := 0; i < 63; i++ {
|
||||
carry := (digits[i] + 8) >> 4
|
||||
digits[i] -= carry << 4
|
||||
digits[i+1] += carry
|
||||
}
|
||||
|
||||
return digits
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,214 @@
|
|||
// Copyright (c) 2019 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 edwards25519
|
||||
|
||||
import "sync"
|
||||
|
||||
// basepointTable is a set of 32 affineLookupTables, where table i is generated
|
||||
// from 256i * basepoint. It is precomputed the first time it's used.
|
||||
func basepointTable() *[32]affineLookupTable {
|
||||
basepointTablePrecomp.initOnce.Do(func() {
|
||||
p := NewGeneratorPoint()
|
||||
for i := 0; i < 32; i++ {
|
||||
basepointTablePrecomp.table[i].FromP3(p)
|
||||
for j := 0; j < 8; j++ {
|
||||
p.Add(p, p)
|
||||
}
|
||||
}
|
||||
})
|
||||
return &basepointTablePrecomp.table
|
||||
}
|
||||
|
||||
var basepointTablePrecomp struct {
|
||||
table [32]affineLookupTable
|
||||
initOnce sync.Once
|
||||
}
|
||||
|
||||
// ScalarBaseMult sets v = x * B, where B is the canonical generator, and
|
||||
// returns v.
|
||||
//
|
||||
// The scalar multiplication is done in constant time.
|
||||
func (v *Point) ScalarBaseMult(x *Scalar) *Point {
|
||||
basepointTable := basepointTable()
|
||||
|
||||
// Write x = sum(x_i * 16^i) so x*B = sum( B*x_i*16^i )
|
||||
// as described in the Ed25519 paper
|
||||
//
|
||||
// Group even and odd coefficients
|
||||
// x*B = x_0*16^0*B + x_2*16^2*B + ... + x_62*16^62*B
|
||||
// + x_1*16^1*B + x_3*16^3*B + ... + x_63*16^63*B
|
||||
// x*B = x_0*16^0*B + x_2*16^2*B + ... + x_62*16^62*B
|
||||
// + 16*( x_1*16^0*B + x_3*16^2*B + ... + x_63*16^62*B)
|
||||
//
|
||||
// We use a lookup table for each i to get x_i*16^(2*i)*B
|
||||
// and do four doublings to multiply by 16.
|
||||
digits := x.signedRadix16()
|
||||
|
||||
multiple := &affineCached{}
|
||||
tmp1 := &projP1xP1{}
|
||||
tmp2 := &projP2{}
|
||||
|
||||
// Accumulate the odd components first
|
||||
v.Set(NewIdentityPoint())
|
||||
for i := 1; i < 64; i += 2 {
|
||||
basepointTable[i/2].SelectInto(multiple, digits[i])
|
||||
tmp1.AddAffine(v, multiple)
|
||||
v.fromP1xP1(tmp1)
|
||||
}
|
||||
|
||||
// Multiply by 16
|
||||
tmp2.FromP3(v) // tmp2 = v in P2 coords
|
||||
tmp1.Double(tmp2) // tmp1 = 2*v in P1xP1 coords
|
||||
tmp2.FromP1xP1(tmp1) // tmp2 = 2*v in P2 coords
|
||||
tmp1.Double(tmp2) // tmp1 = 4*v in P1xP1 coords
|
||||
tmp2.FromP1xP1(tmp1) // tmp2 = 4*v in P2 coords
|
||||
tmp1.Double(tmp2) // tmp1 = 8*v in P1xP1 coords
|
||||
tmp2.FromP1xP1(tmp1) // tmp2 = 8*v in P2 coords
|
||||
tmp1.Double(tmp2) // tmp1 = 16*v in P1xP1 coords
|
||||
v.fromP1xP1(tmp1) // now v = 16*(odd components)
|
||||
|
||||
// Accumulate the even components
|
||||
for i := 0; i < 64; i += 2 {
|
||||
basepointTable[i/2].SelectInto(multiple, digits[i])
|
||||
tmp1.AddAffine(v, multiple)
|
||||
v.fromP1xP1(tmp1)
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// ScalarMult sets v = x * q, and returns v.
|
||||
//
|
||||
// The scalar multiplication is done in constant time.
|
||||
func (v *Point) ScalarMult(x *Scalar, q *Point) *Point {
|
||||
checkInitialized(q)
|
||||
|
||||
var table projLookupTable
|
||||
table.FromP3(q)
|
||||
|
||||
// Write x = sum(x_i * 16^i)
|
||||
// so x*Q = sum( Q*x_i*16^i )
|
||||
// = Q*x_0 + 16*(Q*x_1 + 16*( ... + Q*x_63) ... )
|
||||
// <------compute inside out---------
|
||||
//
|
||||
// We use the lookup table to get the x_i*Q values
|
||||
// and do four doublings to compute 16*Q
|
||||
digits := x.signedRadix16()
|
||||
|
||||
// Unwrap first loop iteration to save computing 16*identity
|
||||
multiple := &projCached{}
|
||||
tmp1 := &projP1xP1{}
|
||||
tmp2 := &projP2{}
|
||||
table.SelectInto(multiple, digits[63])
|
||||
|
||||
v.Set(NewIdentityPoint())
|
||||
tmp1.Add(v, multiple) // tmp1 = x_63*Q in P1xP1 coords
|
||||
for i := 62; i >= 0; i-- {
|
||||
tmp2.FromP1xP1(tmp1) // tmp2 = (prev) in P2 coords
|
||||
tmp1.Double(tmp2) // tmp1 = 2*(prev) in P1xP1 coords
|
||||
tmp2.FromP1xP1(tmp1) // tmp2 = 2*(prev) in P2 coords
|
||||
tmp1.Double(tmp2) // tmp1 = 4*(prev) in P1xP1 coords
|
||||
tmp2.FromP1xP1(tmp1) // tmp2 = 4*(prev) in P2 coords
|
||||
tmp1.Double(tmp2) // tmp1 = 8*(prev) in P1xP1 coords
|
||||
tmp2.FromP1xP1(tmp1) // tmp2 = 8*(prev) in P2 coords
|
||||
tmp1.Double(tmp2) // tmp1 = 16*(prev) in P1xP1 coords
|
||||
v.fromP1xP1(tmp1) // v = 16*(prev) in P3 coords
|
||||
table.SelectInto(multiple, digits[i])
|
||||
tmp1.Add(v, multiple) // tmp1 = x_i*Q + 16*(prev) in P1xP1 coords
|
||||
}
|
||||
v.fromP1xP1(tmp1)
|
||||
return v
|
||||
}
|
||||
|
||||
// basepointNafTable is the nafLookupTable8 for the basepoint.
|
||||
// It is precomputed the first time it's used.
|
||||
func basepointNafTable() *nafLookupTable8 {
|
||||
basepointNafTablePrecomp.initOnce.Do(func() {
|
||||
basepointNafTablePrecomp.table.FromP3(NewGeneratorPoint())
|
||||
})
|
||||
return &basepointNafTablePrecomp.table
|
||||
}
|
||||
|
||||
var basepointNafTablePrecomp struct {
|
||||
table nafLookupTable8
|
||||
initOnce sync.Once
|
||||
}
|
||||
|
||||
// VarTimeDoubleScalarBaseMult sets v = a * A + b * B, where B is the canonical
|
||||
// generator, and returns v.
|
||||
//
|
||||
// Execution time depends on the inputs.
|
||||
func (v *Point) VarTimeDoubleScalarBaseMult(a *Scalar, A *Point, b *Scalar) *Point {
|
||||
checkInitialized(A)
|
||||
|
||||
// Similarly to the single variable-base approach, we compute
|
||||
// digits and use them with a lookup table. However, because
|
||||
// we are allowed to do variable-time operations, we don't
|
||||
// need constant-time lookups or constant-time digit
|
||||
// computations.
|
||||
//
|
||||
// So we use a non-adjacent form of some width w instead of
|
||||
// radix 16. This is like a binary representation (one digit
|
||||
// for each binary place) but we allow the digits to grow in
|
||||
// magnitude up to 2^{w-1} so that the nonzero digits are as
|
||||
// sparse as possible. Intuitively, this "condenses" the
|
||||
// "mass" of the scalar onto sparse coefficients (meaning
|
||||
// fewer additions).
|
||||
|
||||
basepointNafTable := basepointNafTable()
|
||||
var aTable nafLookupTable5
|
||||
aTable.FromP3(A)
|
||||
// Because the basepoint is fixed, we can use a wider NAF
|
||||
// corresponding to a bigger table.
|
||||
aNaf := a.nonAdjacentForm(5)
|
||||
bNaf := b.nonAdjacentForm(8)
|
||||
|
||||
// Find the first nonzero coefficient.
|
||||
i := 255
|
||||
for j := i; j >= 0; j-- {
|
||||
if aNaf[j] != 0 || bNaf[j] != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
multA := &projCached{}
|
||||
multB := &affineCached{}
|
||||
tmp1 := &projP1xP1{}
|
||||
tmp2 := &projP2{}
|
||||
tmp2.Zero()
|
||||
|
||||
// Move from high to low bits, doubling the accumulator
|
||||
// at each iteration and checking whether there is a nonzero
|
||||
// coefficient to look up a multiple of.
|
||||
for ; i >= 0; i-- {
|
||||
tmp1.Double(tmp2)
|
||||
|
||||
// Only update v if we have a nonzero coeff to add in.
|
||||
if aNaf[i] > 0 {
|
||||
v.fromP1xP1(tmp1)
|
||||
aTable.SelectInto(multA, aNaf[i])
|
||||
tmp1.Add(v, multA)
|
||||
} else if aNaf[i] < 0 {
|
||||
v.fromP1xP1(tmp1)
|
||||
aTable.SelectInto(multA, -aNaf[i])
|
||||
tmp1.Sub(v, multA)
|
||||
}
|
||||
|
||||
if bNaf[i] > 0 {
|
||||
v.fromP1xP1(tmp1)
|
||||
basepointNafTable.SelectInto(multB, bNaf[i])
|
||||
tmp1.AddAffine(v, multB)
|
||||
} else if bNaf[i] < 0 {
|
||||
v.fromP1xP1(tmp1)
|
||||
basepointNafTable.SelectInto(multB, -bNaf[i])
|
||||
tmp1.SubAffine(v, multB)
|
||||
}
|
||||
|
||||
tmp2.FromP1xP1(tmp1)
|
||||
}
|
||||
|
||||
v.fromP2(tmp2)
|
||||
return v
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
// Copyright (c) 2019 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 edwards25519
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
)
|
||||
|
||||
// A dynamic lookup table for variable-base, constant-time scalar muls.
|
||||
type projLookupTable struct {
|
||||
points [8]projCached
|
||||
}
|
||||
|
||||
// A precomputed lookup table for fixed-base, constant-time scalar muls.
|
||||
type affineLookupTable struct {
|
||||
points [8]affineCached
|
||||
}
|
||||
|
||||
// A dynamic lookup table for variable-base, variable-time scalar muls.
|
||||
type nafLookupTable5 struct {
|
||||
points [8]projCached
|
||||
}
|
||||
|
||||
// A precomputed lookup table for fixed-base, variable-time scalar muls.
|
||||
type nafLookupTable8 struct {
|
||||
points [64]affineCached
|
||||
}
|
||||
|
||||
// Constructors.
|
||||
|
||||
// Builds a lookup table at runtime. Fast.
|
||||
func (v *projLookupTable) FromP3(q *Point) {
|
||||
// Goal: v.points[i] = (i+1)*Q, i.e., Q, 2Q, ..., 8Q
|
||||
// This allows lookup of -8Q, ..., -Q, 0, Q, ..., 8Q
|
||||
v.points[0].FromP3(q)
|
||||
tmpP3 := Point{}
|
||||
tmpP1xP1 := projP1xP1{}
|
||||
for i := 0; i < 7; i++ {
|
||||
// Compute (i+1)*Q as Q + i*Q and convert to a projCached
|
||||
// This is needlessly complicated because the API has explicit
|
||||
// receivers instead of creating stack objects and relying on RVO
|
||||
v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.Add(q, &v.points[i])))
|
||||
}
|
||||
}
|
||||
|
||||
// This is not optimised for speed; fixed-base tables should be precomputed.
|
||||
func (v *affineLookupTable) FromP3(q *Point) {
|
||||
// Goal: v.points[i] = (i+1)*Q, i.e., Q, 2Q, ..., 8Q
|
||||
// This allows lookup of -8Q, ..., -Q, 0, Q, ..., 8Q
|
||||
v.points[0].FromP3(q)
|
||||
tmpP3 := Point{}
|
||||
tmpP1xP1 := projP1xP1{}
|
||||
for i := 0; i < 7; i++ {
|
||||
// Compute (i+1)*Q as Q + i*Q and convert to affineCached
|
||||
v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.AddAffine(q, &v.points[i])))
|
||||
}
|
||||
}
|
||||
|
||||
// Builds a lookup table at runtime. Fast.
|
||||
func (v *nafLookupTable5) FromP3(q *Point) {
|
||||
// Goal: v.points[i] = (2*i+1)*Q, i.e., Q, 3Q, 5Q, ..., 15Q
|
||||
// This allows lookup of -15Q, ..., -3Q, -Q, 0, Q, 3Q, ..., 15Q
|
||||
v.points[0].FromP3(q)
|
||||
q2 := Point{}
|
||||
q2.Add(q, q)
|
||||
tmpP3 := Point{}
|
||||
tmpP1xP1 := projP1xP1{}
|
||||
for i := 0; i < 7; i++ {
|
||||
v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.Add(&q2, &v.points[i])))
|
||||
}
|
||||
}
|
||||
|
||||
// This is not optimised for speed; fixed-base tables should be precomputed.
|
||||
func (v *nafLookupTable8) FromP3(q *Point) {
|
||||
v.points[0].FromP3(q)
|
||||
q2 := Point{}
|
||||
q2.Add(q, q)
|
||||
tmpP3 := Point{}
|
||||
tmpP1xP1 := projP1xP1{}
|
||||
for i := 0; i < 63; i++ {
|
||||
v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.AddAffine(&q2, &v.points[i])))
|
||||
}
|
||||
}
|
||||
|
||||
// Selectors.
|
||||
|
||||
// Set dest to x*Q, where -8 <= x <= 8, in constant time.
|
||||
func (v *projLookupTable) SelectInto(dest *projCached, x int8) {
|
||||
// Compute xabs = |x|
|
||||
xmask := x >> 7
|
||||
xabs := uint8((x + xmask) ^ xmask)
|
||||
|
||||
dest.Zero()
|
||||
for j := 1; j <= 8; j++ {
|
||||
// Set dest = j*Q if |x| = j
|
||||
cond := subtle.ConstantTimeByteEq(xabs, uint8(j))
|
||||
dest.Select(&v.points[j-1], dest, cond)
|
||||
}
|
||||
// Now dest = |x|*Q, conditionally negate to get x*Q
|
||||
dest.CondNeg(int(xmask & 1))
|
||||
}
|
||||
|
||||
// Set dest to x*Q, where -8 <= x <= 8, in constant time.
|
||||
func (v *affineLookupTable) SelectInto(dest *affineCached, x int8) {
|
||||
// Compute xabs = |x|
|
||||
xmask := x >> 7
|
||||
xabs := uint8((x + xmask) ^ xmask)
|
||||
|
||||
dest.Zero()
|
||||
for j := 1; j <= 8; j++ {
|
||||
// Set dest = j*Q if |x| = j
|
||||
cond := subtle.ConstantTimeByteEq(xabs, uint8(j))
|
||||
dest.Select(&v.points[j-1], dest, cond)
|
||||
}
|
||||
// Now dest = |x|*Q, conditionally negate to get x*Q
|
||||
dest.CondNeg(int(xmask & 1))
|
||||
}
|
||||
|
||||
// Given odd x with 0 < x < 2^4, return x*Q (in variable time).
|
||||
func (v *nafLookupTable5) SelectInto(dest *projCached, x int8) {
|
||||
*dest = v.points[x/2]
|
||||
}
|
||||
|
||||
// Given odd x with 0 < x < 2^7, return x*Q (in variable time).
|
||||
func (v *nafLookupTable8) SelectInto(dest *affineCached, x int8) {
|
||||
*dest = v.points[x/2]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
squirrel.test
|
|
@ -0,0 +1,30 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- 1.13.x
|
||||
|
||||
services:
|
||||
- mysql
|
||||
- postgresql
|
||||
|
||||
# Setting sudo access to false will let Travis CI use containers rather than
|
||||
# VMs to run the tests. For more details see:
|
||||
# - http://docs.travis-ci.com/user/workers/container-based-infrastructure/
|
||||
# - http://docs.travis-ci.com/user/workers/standard-infrastructure/
|
||||
sudo: false
|
||||
|
||||
before_script:
|
||||
- mysql -e 'CREATE DATABASE squirrel;'
|
||||
- psql -c 'CREATE DATABASE squirrel;' -U postgres
|
||||
|
||||
script:
|
||||
- go test
|
||||
- cd integration
|
||||
- go test -args -driver sqlite3
|
||||
- go test -args -driver mysql -dataSource travis@/squirrel
|
||||
- go test -args -driver postgres -dataSource 'postgres://postgres@localhost/squirrel?sslmode=disable'
|
||||
|
||||
notifications:
|
||||
irc: "irc.freenode.net#masterminds"
|
|
@ -0,0 +1,23 @@
|
|||
MIT License
|
||||
|
||||
Squirrel: The Masterminds
|
||||
Copyright (c) 2014-2015, Lann Martin. Copyright (C) 2015-2016, Google. Copyright (C) 2015, Matt Farina and Matt Butcher.
|
||||
|
||||
|
||||
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.
|
|
@ -0,0 +1,142 @@
|
|||
[](https://masterminds.github.io/stability/maintenance.html)
|
||||
### Squirrel is "complete".
|
||||
Bug fixes will still be merged (slowly). Bug reports are welcome, but I will not necessarily respond to them. If another fork (or substantially similar project) actively improves on what Squirrel does, let me know and I may link to it here.
|
||||
|
||||
|
||||
# Squirrel - fluent SQL generator for Go
|
||||
|
||||
```go
|
||||
import "github.com/Masterminds/squirrel"
|
||||
```
|
||||
|
||||
|
||||
[](https://godoc.org/github.com/Masterminds/squirrel)
|
||||
[](https://travis-ci.org/Masterminds/squirrel)
|
||||
|
||||
**Squirrel is not an ORM.** For an application of Squirrel, check out
|
||||
[structable, a table-struct mapper](https://github.com/Masterminds/structable)
|
||||
|
||||
|
||||
Squirrel helps you build SQL queries from composable parts:
|
||||
|
||||
```go
|
||||
import sq "github.com/Masterminds/squirrel"
|
||||
|
||||
users := sq.Select("*").From("users").Join("emails USING (email_id)")
|
||||
|
||||
active := users.Where(sq.Eq{"deleted_at": nil})
|
||||
|
||||
sql, args, err := active.ToSql()
|
||||
|
||||
sql == "SELECT * FROM users JOIN emails USING (email_id) WHERE deleted_at IS NULL"
|
||||
```
|
||||
|
||||
```go
|
||||
sql, args, err := sq.
|
||||
Insert("users").Columns("name", "age").
|
||||
Values("moe", 13).Values("larry", sq.Expr("? + 5", 12)).
|
||||
ToSql()
|
||||
|
||||
sql == "INSERT INTO users (name,age) VALUES (?,?),(?,? + 5)"
|
||||
```
|
||||
|
||||
Squirrel can also execute queries directly:
|
||||
|
||||
```go
|
||||
stooges := users.Where(sq.Eq{"username": []string{"moe", "larry", "curly", "shemp"}})
|
||||
three_stooges := stooges.Limit(3)
|
||||
rows, err := three_stooges.RunWith(db).Query()
|
||||
|
||||
// Behaves like:
|
||||
rows, err := db.Query("SELECT * FROM users WHERE username IN (?,?,?,?) LIMIT 3",
|
||||
"moe", "larry", "curly", "shemp")
|
||||
```
|
||||
|
||||
Squirrel makes conditional query building a breeze:
|
||||
|
||||
```go
|
||||
if len(q) > 0 {
|
||||
users = users.Where("name LIKE ?", fmt.Sprint("%", q, "%"))
|
||||
}
|
||||
```
|
||||
|
||||
Squirrel wants to make your life easier:
|
||||
|
||||
```go
|
||||
// StmtCache caches Prepared Stmts for you
|
||||
dbCache := sq.NewStmtCache(db)
|
||||
|
||||
// StatementBuilder keeps your syntax neat
|
||||
mydb := sq.StatementBuilder.RunWith(dbCache)
|
||||
select_users := mydb.Select("*").From("users")
|
||||
```
|
||||
|
||||
Squirrel loves PostgreSQL:
|
||||
|
||||
```go
|
||||
psql := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
|
||||
|
||||
// You use question marks for placeholders...
|
||||
sql, _, _ := psql.Select("*").From("elephants").Where("name IN (?,?)", "Dumbo", "Verna").ToSql()
|
||||
|
||||
/// ...squirrel replaces them using PlaceholderFormat.
|
||||
sql == "SELECT * FROM elephants WHERE name IN ($1,$2)"
|
||||
|
||||
|
||||
/// You can retrieve id ...
|
||||
query := sq.Insert("nodes").
|
||||
Columns("uuid", "type", "data").
|
||||
Values(node.Uuid, node.Type, node.Data).
|
||||
Suffix("RETURNING \"id\"").
|
||||
RunWith(m.db).
|
||||
PlaceholderFormat(sq.Dollar)
|
||||
|
||||
query.QueryRow().Scan(&node.id)
|
||||
```
|
||||
|
||||
You can escape question marks by inserting two question marks:
|
||||
|
||||
```sql
|
||||
SELECT * FROM nodes WHERE meta->'format' ??| array[?,?]
|
||||
```
|
||||
|
||||
will generate with the Dollar Placeholder:
|
||||
|
||||
```sql
|
||||
SELECT * FROM nodes WHERE meta->'format' ?| array[$1,$2]
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
* **How can I build an IN query on composite keys / tuples, e.g. `WHERE (col1, col2) IN ((1,2),(3,4))`? ([#104](https://github.com/Masterminds/squirrel/issues/104))**
|
||||
|
||||
Squirrel does not explicitly support tuples, but you can get the same effect with e.g.:
|
||||
|
||||
```go
|
||||
sq.Or{
|
||||
sq.Eq{"col1": 1, "col2": 2},
|
||||
sq.Eq{"col1": 3, "col2": 4}}
|
||||
```
|
||||
|
||||
```sql
|
||||
WHERE (col1 = 1 AND col2 = 2) OR (col1 = 3 AND col2 = 4)
|
||||
```
|
||||
|
||||
(which should produce the same query plan as the tuple version)
|
||||
|
||||
* **Why doesn't `Eq{"mynumber": []uint8{1,2,3}}` turn into an `IN` query? ([#114](https://github.com/Masterminds/squirrel/issues/114))**
|
||||
|
||||
Values of type `[]byte` are handled specially by `database/sql`. In Go, [`byte` is just an alias of `uint8`](https://golang.org/pkg/builtin/#byte), so there is no way to distinguish `[]uint8` from `[]byte`.
|
||||
|
||||
* **Some features are poorly documented!**
|
||||
|
||||
This isn't a frequent complaints section!
|
||||
|
||||
* **Some features are poorly documented?**
|
||||
|
||||
Yes. The tests should be considered a part of the documentation; take a look at those for ideas on how to express more complex queries.
|
||||
|
||||
## License
|
||||
|
||||
Squirrel is released under the
|
||||
[MIT License](http://www.opensource.org/licenses/MIT).
|
|
@ -0,0 +1,128 @@
|
|||
package squirrel
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
|
||||
"github.com/lann/builder"
|
||||
)
|
||||
|
||||
func init() {
|
||||
builder.Register(CaseBuilder{}, caseData{})
|
||||
}
|
||||
|
||||
// sqlizerBuffer is a helper that allows to write many Sqlizers one by one
|
||||
// without constant checks for errors that may come from Sqlizer
|
||||
type sqlizerBuffer struct {
|
||||
bytes.Buffer
|
||||
args []interface{}
|
||||
err error
|
||||
}
|
||||
|
||||
// WriteSql converts Sqlizer to SQL strings and writes it to buffer
|
||||
func (b *sqlizerBuffer) WriteSql(item Sqlizer) {
|
||||
if b.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var str string
|
||||
var args []interface{}
|
||||
str, args, b.err = nestedToSql(item)
|
||||
|
||||
if b.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
b.WriteString(str)
|
||||
b.WriteByte(' ')
|
||||
b.args = append(b.args, args...)
|
||||
}
|
||||
|
||||
func (b *sqlizerBuffer) ToSql() (string, []interface{}, error) {
|
||||
return b.String(), b.args, b.err
|
||||
}
|
||||
|
||||
// whenPart is a helper structure to describe SQLs "WHEN ... THEN ..." expression
|
||||
type whenPart struct {
|
||||
when Sqlizer
|
||||
then Sqlizer
|
||||
}
|
||||
|
||||
func newWhenPart(when interface{}, then interface{}) whenPart {
|
||||
return whenPart{newPart(when), newPart(then)}
|
||||
}
|
||||
|
||||
// caseData holds all the data required to build a CASE SQL construct
|
||||
type caseData struct {
|
||||
What Sqlizer
|
||||
WhenParts []whenPart
|
||||
Else Sqlizer
|
||||
}
|
||||
|
||||
// ToSql implements Sqlizer
|
||||
func (d *caseData) ToSql() (sqlStr string, args []interface{}, err error) {
|
||||
if len(d.WhenParts) == 0 {
|
||||
err = errors.New("case expression must contain at lease one WHEN clause")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
sql := sqlizerBuffer{}
|
||||
|
||||
sql.WriteString("CASE ")
|
||||
if d.What != nil {
|
||||
sql.WriteSql(d.What)
|
||||
}
|
||||
|
||||
for _, p := range d.WhenParts {
|
||||
sql.WriteString("WHEN ")
|
||||
sql.WriteSql(p.when)
|
||||
sql.WriteString("THEN ")
|
||||
sql.WriteSql(p.then)
|
||||
}
|
||||
|
||||
if d.Else != nil {
|
||||
sql.WriteString("ELSE ")
|
||||
sql.WriteSql(d.Else)
|
||||
}
|
||||
|
||||
sql.WriteString("END")
|
||||
|
||||
return sql.ToSql()
|
||||
}
|
||||
|
||||
// CaseBuilder builds SQL CASE construct which could be used as parts of queries.
|
||||
type CaseBuilder builder.Builder
|
||||
|
||||
// ToSql builds the query into a SQL string and bound args.
|
||||
func (b CaseBuilder) ToSql() (string, []interface{}, error) {
|
||||
data := builder.GetStruct(b).(caseData)
|
||||
return data.ToSql()
|
||||
}
|
||||
|
||||
// MustSql builds the query into a SQL string and bound args.
|
||||
// It panics if there are any errors.
|
||||
func (b CaseBuilder) MustSql() (string, []interface{}) {
|
||||
sql, args, err := b.ToSql()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sql, args
|
||||
}
|
||||
|
||||
// what sets optional value for CASE construct "CASE [value] ..."
|
||||
func (b CaseBuilder) what(expr interface{}) CaseBuilder {
|
||||
return builder.Set(b, "What", newPart(expr)).(CaseBuilder)
|
||||
}
|
||||
|
||||
// When adds "WHEN ... THEN ..." part to CASE construct
|
||||
func (b CaseBuilder) When(when interface{}, then interface{}) CaseBuilder {
|
||||
// TODO: performance hint: replace slice of WhenPart with just slice of parts
|
||||
// where even indices of the slice belong to "when"s and odd indices belong to "then"s
|
||||
return builder.Append(b, "WhenParts", newWhenPart(when, then)).(CaseBuilder)
|
||||
}
|
||||
|
||||
// What sets optional "ELSE ..." part for CASE construct
|
||||
func (b CaseBuilder) Else(expr interface{}) CaseBuilder {
|
||||
return builder.Set(b, "Else", newPart(expr)).(CaseBuilder)
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
package squirrel
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/lann/builder"
|
||||
)
|
||||
|
||||
type deleteData struct {
|
||||
PlaceholderFormat PlaceholderFormat
|
||||
RunWith BaseRunner
|
||||
Prefixes []Sqlizer
|
||||
From string
|
||||
WhereParts []Sqlizer
|
||||
OrderBys []string
|
||||
Limit string
|
||||
Offset string
|
||||
Suffixes []Sqlizer
|
||||
}
|
||||
|
||||
func (d *deleteData) Exec() (sql.Result, error) {
|
||||
if d.RunWith == nil {
|
||||
return nil, RunnerNotSet
|
||||
}
|
||||
return ExecWith(d.RunWith, d)
|
||||
}
|
||||
|
||||
func (d *deleteData) ToSql() (sqlStr string, args []interface{}, err error) {
|
||||
if len(d.From) == 0 {
|
||||
err = fmt.Errorf("delete statements must specify a From table")
|
||||
return
|
||||
}
|
||||
|
||||
sql := &bytes.Buffer{}
|
||||
|
||||
if len(d.Prefixes) > 0 {
|
||||
args, err = appendToSql(d.Prefixes, sql, " ", args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sql.WriteString(" ")
|
||||
}
|
||||
|
||||
sql.WriteString("DELETE FROM ")
|
||||
sql.WriteString(d.From)
|
||||
|
||||
if len(d.WhereParts) > 0 {
|
||||
sql.WriteString(" WHERE ")
|
||||
args, err = appendToSql(d.WhereParts, sql, " AND ", args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(d.OrderBys) > 0 {
|
||||
sql.WriteString(" ORDER BY ")
|
||||
sql.WriteString(strings.Join(d.OrderBys, ", "))
|
||||
}
|
||||
|
||||
if len(d.Limit) > 0 {
|
||||
sql.WriteString(" LIMIT ")
|
||||
sql.WriteString(d.Limit)
|
||||
}
|
||||
|
||||
if len(d.Offset) > 0 {
|
||||
sql.WriteString(" OFFSET ")
|
||||
sql.WriteString(d.Offset)
|
||||
}
|
||||
|
||||
if len(d.Suffixes) > 0 {
|
||||
sql.WriteString(" ")
|
||||
args, err = appendToSql(d.Suffixes, sql, " ", args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
sqlStr, err = d.PlaceholderFormat.ReplacePlaceholders(sql.String())
|
||||
return
|
||||
}
|
||||
|
||||
// Builder
|
||||
|
||||
// DeleteBuilder builds SQL DELETE statements.
|
||||
type DeleteBuilder builder.Builder
|
||||
|
||||
func init() {
|
||||
builder.Register(DeleteBuilder{}, deleteData{})
|
||||
}
|
||||
|
||||
// Format methods
|
||||
|
||||
// PlaceholderFormat sets PlaceholderFormat (e.g. Question or Dollar) for the
|
||||
// query.
|
||||
func (b DeleteBuilder) PlaceholderFormat(f PlaceholderFormat) DeleteBuilder {
|
||||
return builder.Set(b, "PlaceholderFormat", f).(DeleteBuilder)
|
||||
}
|
||||
|
||||
// Runner methods
|
||||
|
||||
// RunWith sets a Runner (like database/sql.DB) to be used with e.g. Exec.
|
||||
func (b DeleteBuilder) RunWith(runner BaseRunner) DeleteBuilder {
|
||||
return setRunWith(b, runner).(DeleteBuilder)
|
||||
}
|
||||
|
||||
// Exec builds and Execs the query with the Runner set by RunWith.
|
||||
func (b DeleteBuilder) Exec() (sql.Result, error) {
|
||||
data := builder.GetStruct(b).(deleteData)
|
||||
return data.Exec()
|
||||
}
|
||||
|
||||
// SQL methods
|
||||
|
||||
// ToSql builds the query into a SQL string and bound args.
|
||||
func (b DeleteBuilder) ToSql() (string, []interface{}, error) {
|
||||
data := builder.GetStruct(b).(deleteData)
|
||||
return data.ToSql()
|
||||
}
|
||||
|
||||
// MustSql builds the query into a SQL string and bound args.
|
||||
// It panics if there are any errors.
|
||||
func (b DeleteBuilder) MustSql() (string, []interface{}) {
|
||||
sql, args, err := b.ToSql()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sql, args
|
||||
}
|
||||
|
||||
// Prefix adds an expression to the beginning of the query
|
||||
func (b DeleteBuilder) Prefix(sql string, args ...interface{}) DeleteBuilder {
|
||||
return b.PrefixExpr(Expr(sql, args...))
|
||||
}
|
||||
|
||||
// PrefixExpr adds an expression to the very beginning of the query
|
||||
func (b DeleteBuilder) PrefixExpr(expr Sqlizer) DeleteBuilder {
|
||||
return builder.Append(b, "Prefixes", expr).(DeleteBuilder)
|
||||
}
|
||||
|
||||
// From sets the table to be deleted from.
|
||||
func (b DeleteBuilder) From(from string) DeleteBuilder {
|
||||
return builder.Set(b, "From", from).(DeleteBuilder)
|
||||
}
|
||||
|
||||
// Where adds WHERE expressions to the query.
|
||||
//
|
||||
// See SelectBuilder.Where for more information.
|
||||
func (b DeleteBuilder) Where(pred interface{}, args ...interface{}) DeleteBuilder {
|
||||
return builder.Append(b, "WhereParts", newWherePart(pred, args...)).(DeleteBuilder)
|
||||
}
|
||||
|
||||
// OrderBy adds ORDER BY expressions to the query.
|
||||
func (b DeleteBuilder) OrderBy(orderBys ...string) DeleteBuilder {
|
||||
return builder.Extend(b, "OrderBys", orderBys).(DeleteBuilder)
|
||||
}
|
||||
|
||||
// Limit sets a LIMIT clause on the query.
|
||||
func (b DeleteBuilder) Limit(limit uint64) DeleteBuilder {
|
||||
return builder.Set(b, "Limit", fmt.Sprintf("%d", limit)).(DeleteBuilder)
|
||||
}
|
||||
|
||||
// Offset sets a OFFSET clause on the query.
|
||||
func (b DeleteBuilder) Offset(offset uint64) DeleteBuilder {
|
||||
return builder.Set(b, "Offset", fmt.Sprintf("%d", offset)).(DeleteBuilder)
|
||||
}
|
||||
|
||||
// Suffix adds an expression to the end of the query
|
||||
func (b DeleteBuilder) Suffix(sql string, args ...interface{}) DeleteBuilder {
|
||||
return b.SuffixExpr(Expr(sql, args...))
|
||||
}
|
||||
|
||||
// SuffixExpr adds an expression to the end of the query
|
||||
func (b DeleteBuilder) SuffixExpr(expr Sqlizer) DeleteBuilder {
|
||||
return builder.Append(b, "Suffixes", expr).(DeleteBuilder)
|
||||
}
|
||||
|
||||
func (b DeleteBuilder) Query() (*sql.Rows, error) {
|
||||
data := builder.GetStruct(b).(deleteData)
|
||||
return data.Query()
|
||||
}
|
||||
|
||||
func (d *deleteData) Query() (*sql.Rows, error) {
|
||||
if d.RunWith == nil {
|
||||
return nil, RunnerNotSet
|
||||
}
|
||||
return QueryWith(d.RunWith, d)
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
// +build go1.8
|
||||
|
||||
package squirrel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/lann/builder"
|
||||
)
|
||||
|
||||
func (d *deleteData) ExecContext(ctx context.Context) (sql.Result, error) {
|
||||
if d.RunWith == nil {
|
||||
return nil, RunnerNotSet
|
||||
}
|
||||
ctxRunner, ok := d.RunWith.(ExecerContext)
|
||||
if !ok {
|
||||
return nil, NoContextSupport
|
||||
}
|
||||
return ExecContextWith(ctx, ctxRunner, d)
|
||||
}
|
||||
|
||||
func (d *deleteData) QueryContext(ctx context.Context) (*sql.Rows, error) {
|
||||
if d.RunWith == nil {
|
||||
return nil, RunnerNotSet
|
||||
}
|
||||
ctxRunner, ok := d.RunWith.(QueryerContext)
|
||||
if !ok {
|
||||
return nil, NoContextSupport
|
||||
}
|
||||
return QueryContextWith(ctx, ctxRunner, d)
|
||||
}
|
||||
|
||||
func (d *deleteData) QueryRowContext(ctx context.Context) RowScanner {
|
||||
if d.RunWith == nil {
|
||||
return &Row{err: RunnerNotSet}
|
||||
}
|
||||
queryRower, ok := d.RunWith.(QueryRowerContext)
|
||||
if !ok {
|
||||
if _, ok := d.RunWith.(QueryerContext); !ok {
|
||||
return &Row{err: RunnerNotQueryRunner}
|
||||
}
|
||||
return &Row{err: NoContextSupport}
|
||||
}
|
||||
return QueryRowContextWith(ctx, queryRower, d)
|
||||
}
|
||||
|
||||
// ExecContext builds and ExecContexts the query with the Runner set by RunWith.
|
||||
func (b DeleteBuilder) ExecContext(ctx context.Context) (sql.Result, error) {
|
||||
data := builder.GetStruct(b).(deleteData)
|
||||
return data.ExecContext(ctx)
|
||||
}
|
||||
|
||||
// QueryContext builds and QueryContexts the query with the Runner set by RunWith.
|
||||
func (b DeleteBuilder) QueryContext(ctx context.Context) (*sql.Rows, error) {
|
||||
data := builder.GetStruct(b).(deleteData)
|
||||
return data.QueryContext(ctx)
|
||||
}
|
||||
|
||||
// QueryRowContext builds and QueryRowContexts the query with the Runner set by RunWith.
|
||||
func (b DeleteBuilder) QueryRowContext(ctx context.Context) RowScanner {
|
||||
data := builder.GetStruct(b).(deleteData)
|
||||
return data.QueryRowContext(ctx)
|
||||
}
|
||||
|
||||
// ScanContext is a shortcut for QueryRowContext().Scan.
|
||||
func (b DeleteBuilder) ScanContext(ctx context.Context, dest ...interface{}) error {
|
||||
return b.QueryRowContext(ctx).Scan(dest...)
|
||||
}
|
|
@ -0,0 +1,419 @@
|
|||
package squirrel
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// Portable true/false literals.
|
||||
sqlTrue = "(1=1)"
|
||||
sqlFalse = "(1=0)"
|
||||
)
|
||||
|
||||
type expr struct {
|
||||
sql string
|
||||
args []interface{}
|
||||
}
|
||||
|
||||
// Expr builds an expression from a SQL fragment and arguments.
|
||||
//
|
||||
// Ex:
|
||||
// Expr("FROM_UNIXTIME(?)", t)
|
||||
func Expr(sql string, args ...interface{}) Sqlizer {
|
||||
return expr{sql: sql, args: args}
|
||||
}
|
||||
|
||||
func (e expr) ToSql() (sql string, args []interface{}, err error) {
|
||||
simple := true
|
||||
for _, arg := range e.args {
|
||||
if _, ok := arg.(Sqlizer); ok {
|
||||
simple = false
|
||||
}
|
||||
}
|
||||
if simple {
|
||||
return e.sql, e.args, nil
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
ap := e.args
|
||||
sp := e.sql
|
||||
|
||||
var isql string
|
||||
var iargs []interface{}
|
||||
|
||||
for err == nil && len(ap) > 0 && len(sp) > 0 {
|
||||
i := strings.Index(sp, "?")
|
||||
if i < 0 {
|
||||
// no more placeholders
|
||||
break
|
||||
}
|
||||
if len(sp) > i+1 && sp[i+1:i+2] == "?" {
|
||||
// escaped "??"; append it and step past
|
||||
buf.WriteString(sp[:i+2])
|
||||
sp = sp[i+2:]
|
||||
continue
|
||||
}
|
||||
|
||||
if as, ok := ap[0].(Sqlizer); ok {
|
||||
// sqlizer argument; expand it and append the result
|
||||
isql, iargs, err = as.ToSql()
|
||||
buf.WriteString(sp[:i])
|
||||
buf.WriteString(isql)
|
||||
args = append(args, iargs...)
|
||||
} else {
|
||||
// normal argument; append it and the placeholder
|
||||
buf.WriteString(sp[:i+1])
|
||||
args = append(args, ap[0])
|
||||
}
|
||||
|
||||
// step past the argument and placeholder
|
||||
ap = ap[1:]
|
||||
sp = sp[i+1:]
|
||||
}
|
||||
|
||||
// append the remaining sql and arguments
|
||||
buf.WriteString(sp)
|
||||
return buf.String(), append(args, ap...), err
|
||||
}
|
||||
|
||||
type concatExpr []interface{}
|
||||
|
||||
func (ce concatExpr) ToSql() (sql string, args []interface{}, err error) {
|
||||
for _, part := range ce {
|
||||
switch p := part.(type) {
|
||||
case string:
|
||||
sql += p
|
||||
case Sqlizer:
|
||||
pSql, pArgs, err := p.ToSql()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
sql += pSql
|
||||
args = append(args, pArgs...)
|
||||
default:
|
||||
return "", nil, fmt.Errorf("%#v is not a string or Sqlizer", part)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ConcatExpr builds an expression by concatenating strings and other expressions.
|
||||
//
|
||||
// Ex:
|
||||
// name_expr := Expr("CONCAT(?, ' ', ?)", firstName, lastName)
|
||||
// ConcatExpr("COALESCE(full_name,", name_expr, ")")
|
||||
func ConcatExpr(parts ...interface{}) concatExpr {
|
||||
return concatExpr(parts)
|
||||
}
|
||||
|
||||
// aliasExpr helps to alias part of SQL query generated with underlying "expr"
|
||||
type aliasExpr struct {
|
||||
expr Sqlizer
|
||||
alias string
|
||||
}
|
||||
|
||||
// Alias allows to define alias for column in SelectBuilder. Useful when column is
|
||||
// defined as complex expression like IF or CASE
|
||||
// Ex:
|
||||
// .Column(Alias(caseStmt, "case_column"))
|
||||
func Alias(expr Sqlizer, alias string) aliasExpr {
|
||||
return aliasExpr{expr, alias}
|
||||
}
|
||||
|
||||
func (e aliasExpr) ToSql() (sql string, args []interface{}, err error) {
|
||||
sql, args, err = e.expr.ToSql()
|
||||
if err == nil {
|
||||
sql = fmt.Sprintf("(%s) AS %s", sql, e.alias)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Eq is syntactic sugar for use with Where/Having/Set methods.
|
||||
type Eq map[string]interface{}
|
||||
|
||||
func (eq Eq) toSQL(useNotOpr bool) (sql string, args []interface{}, err error) {
|
||||
if len(eq) == 0 {
|
||||
// Empty Sql{} evaluates to true.
|
||||
sql = sqlTrue
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
exprs []string
|
||||
equalOpr = "="
|
||||
inOpr = "IN"
|
||||
nullOpr = "IS"
|
||||
inEmptyExpr = sqlFalse
|
||||
)
|
||||
|
||||
if useNotOpr {
|
||||
equalOpr = "<>"
|
||||
inOpr = "NOT IN"
|
||||
nullOpr = "IS NOT"
|
||||
inEmptyExpr = sqlTrue
|
||||
}
|
||||
|
||||
sortedKeys := getSortedKeys(eq)
|
||||
for _, key := range sortedKeys {
|
||||
var expr string
|
||||
val := eq[key]
|
||||
|
||||
switch v := val.(type) {
|
||||
case driver.Valuer:
|
||||
if val, err = v.Value(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
r := reflect.ValueOf(val)
|
||||
if r.Kind() == reflect.Ptr {
|
||||
if r.IsNil() {
|
||||
val = nil
|
||||
} else {
|
||||
val = r.Elem().Interface()
|
||||
}
|
||||
}
|
||||
|
||||
if val == nil {
|
||||
expr = fmt.Sprintf("%s %s NULL", key, nullOpr)
|
||||
} else {
|
||||
if isListType(val) {
|
||||
valVal := reflect.ValueOf(val)
|
||||
if valVal.Len() == 0 {
|
||||
expr = inEmptyExpr
|
||||
if args == nil {
|
||||
args = []interface{}{}
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < valVal.Len(); i++ {
|
||||
args = append(args, valVal.Index(i).Interface())
|
||||
}
|
||||
expr = fmt.Sprintf("%s %s (%s)", key, inOpr, Placeholders(valVal.Len()))
|
||||
}
|
||||
} else {
|
||||
expr = fmt.Sprintf("%s %s ?", key, equalOpr)
|
||||
args = append(args, val)
|
||||
}
|
||||
}
|
||||
exprs = append(exprs, expr)
|
||||
}
|
||||
sql = strings.Join(exprs, " AND ")
|
||||
return
|
||||
}
|
||||
|
||||
func (eq Eq) ToSql() (sql string, args []interface{}, err error) {
|
||||
return eq.toSQL(false)
|
||||
}
|
||||
|
||||
// NotEq is syntactic sugar for use with Where/Having/Set methods.
|
||||
// Ex:
|
||||
// .Where(NotEq{"id": 1}) == "id <> 1"
|
||||
type NotEq Eq
|
||||
|
||||
func (neq NotEq) ToSql() (sql string, args []interface{}, err error) {
|
||||
return Eq(neq).toSQL(true)
|
||||
}
|
||||
|
||||
// Like is syntactic sugar for use with LIKE conditions.
|
||||
// Ex:
|
||||
// .Where(Like{"name": "%irrel"})
|
||||
type Like map[string]interface{}
|
||||
|
||||
func (lk Like) toSql(opr string) (sql string, args []interface{}, err error) {
|
||||
var exprs []string
|
||||
for key, val := range lk {
|
||||
expr := ""
|
||||
|
||||
switch v := val.(type) {
|
||||
case driver.Valuer:
|
||||
if val, err = v.Value(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if val == nil {
|
||||
err = fmt.Errorf("cannot use null with like operators")
|
||||
return
|
||||
} else {
|
||||
if isListType(val) {
|
||||
err = fmt.Errorf("cannot use array or slice with like operators")
|
||||
return
|
||||
} else {
|
||||
expr = fmt.Sprintf("%s %s ?", key, opr)
|
||||
args = append(args, val)
|
||||
}
|
||||
}
|
||||
exprs = append(exprs, expr)
|
||||
}
|
||||
sql = strings.Join(exprs, " AND ")
|
||||
return
|
||||
}
|
||||
|
||||
func (lk Like) ToSql() (sql string, args []interface{}, err error) {
|
||||
return lk.toSql("LIKE")
|
||||
}
|
||||
|
||||
// NotLike is syntactic sugar for use with LIKE conditions.
|
||||
// Ex:
|
||||
// .Where(NotLike{"name": "%irrel"})
|
||||
type NotLike Like
|
||||
|
||||
func (nlk NotLike) ToSql() (sql string, args []interface{}, err error) {
|
||||
return Like(nlk).toSql("NOT LIKE")
|
||||
}
|
||||
|
||||
// ILike is syntactic sugar for use with ILIKE conditions.
|
||||
// Ex:
|
||||
// .Where(ILike{"name": "sq%"})
|
||||
type ILike Like
|
||||
|
||||
func (ilk ILike) ToSql() (sql string, args []interface{}, err error) {
|
||||
return Like(ilk).toSql("ILIKE")
|
||||
}
|
||||
|
||||
// NotILike is syntactic sugar for use with ILIKE conditions.
|
||||
// Ex:
|
||||
// .Where(NotILike{"name": "sq%"})
|
||||
type NotILike Like
|
||||
|
||||
func (nilk NotILike) ToSql() (sql string, args []interface{}, err error) {
|
||||
return Like(nilk).toSql("NOT ILIKE")
|
||||
}
|
||||
|
||||
// Lt is syntactic sugar for use with Where/Having/Set methods.
|
||||
// Ex:
|
||||
// .Where(Lt{"id": 1})
|
||||
type Lt map[string]interface{}
|
||||
|
||||
func (lt Lt) toSql(opposite, orEq bool) (sql string, args []interface{}, err error) {
|
||||
var (
|
||||
exprs []string
|
||||
opr = "<"
|
||||
)
|
||||
|
||||
if opposite {
|
||||
opr = ">"
|
||||
}
|
||||
|
||||
if orEq {
|
||||
opr = fmt.Sprintf("%s%s", opr, "=")
|
||||
}
|
||||
|
||||
sortedKeys := getSortedKeys(lt)
|
||||
for _, key := range sortedKeys {
|
||||
var expr string
|
||||
val := lt[key]
|
||||
|
||||
switch v := val.(type) {
|
||||
case driver.Valuer:
|
||||
if val, err = v.Value(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if val == nil {
|
||||
err = fmt.Errorf("cannot use null with less than or greater than operators")
|
||||
return
|
||||
}
|
||||
if isListType(val) {
|
||||
err = fmt.Errorf("cannot use array or slice with less than or greater than operators")
|
||||
return
|
||||
}
|
||||
expr = fmt.Sprintf("%s %s ?", key, opr)
|
||||
args = append(args, val)
|
||||
|
||||
exprs = append(exprs, expr)
|
||||
}
|
||||
sql = strings.Join(exprs, " AND ")
|
||||
return
|
||||
}
|
||||
|
||||
func (lt Lt) ToSql() (sql string, args []interface{}, err error) {
|
||||
return lt.toSql(false, false)
|
||||
}
|
||||
|
||||
// LtOrEq is syntactic sugar for use with Where/Having/Set methods.
|
||||
// Ex:
|
||||
// .Where(LtOrEq{"id": 1}) == "id <= 1"
|
||||
type LtOrEq Lt
|
||||
|
||||
func (ltOrEq LtOrEq) ToSql() (sql string, args []interface{}, err error) {
|
||||
return Lt(ltOrEq).toSql(false, true)
|
||||
}
|
||||
|
||||
// Gt is syntactic sugar for use with Where/Having/Set methods.
|
||||
// Ex:
|
||||
// .Where(Gt{"id": 1}) == "id > 1"
|
||||
type Gt Lt
|
||||
|
||||
func (gt Gt) ToSql() (sql string, args []interface{}, err error) {
|
||||
return Lt(gt).toSql(true, false)
|
||||
}
|
||||
|
||||
// GtOrEq is syntactic sugar for use with Where/Having/Set methods.
|
||||
// Ex:
|
||||
// .Where(GtOrEq{"id": 1}) == "id >= 1"
|
||||
type GtOrEq Lt
|
||||
|
||||
func (gtOrEq GtOrEq) ToSql() (sql string, args []interface{}, err error) {
|
||||
return Lt(gtOrEq).toSql(true, true)
|
||||
}
|
||||
|
||||
type conj []Sqlizer
|
||||
|
||||
func (c conj) join(sep, defaultExpr string) (sql string, args []interface{}, err error) {
|
||||
if len(c) == 0 {
|
||||
return defaultExpr, []interface{}{}, nil
|
||||
}
|
||||
var sqlParts []string
|
||||
for _, sqlizer := range c {
|
||||
partSQL, partArgs, err := nestedToSql(sqlizer)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if partSQL != "" {
|
||||
sqlParts = append(sqlParts, partSQL)
|
||||
args = append(args, partArgs...)
|
||||
}
|
||||
}
|
||||
if len(sqlParts) > 0 {
|
||||
sql = fmt.Sprintf("(%s)", strings.Join(sqlParts, sep))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// And conjunction Sqlizers
|
||||
type And conj
|
||||
|
||||
func (a And) ToSql() (string, []interface{}, error) {
|
||||
return conj(a).join(" AND ", sqlTrue)
|
||||
}
|
||||
|
||||
// Or conjunction Sqlizers
|
||||
type Or conj
|
||||
|
||||
func (o Or) ToSql() (string, []interface{}, error) {
|
||||
return conj(o).join(" OR ", sqlFalse)
|
||||
}
|
||||
|
||||
func getSortedKeys(exp map[string]interface{}) []string {
|
||||
sortedKeys := make([]string, 0, len(exp))
|
||||
for k := range exp {
|
||||
sortedKeys = append(sortedKeys, k)
|
||||
}
|
||||
sort.Strings(sortedKeys)
|
||||
return sortedKeys
|
||||
}
|
||||
|
||||
func isListType(val interface{}) bool {
|
||||
if driver.IsValue(val) {
|
||||
return false
|
||||
}
|
||||
valVal := reflect.ValueOf(val)
|
||||
return valVal.Kind() == reflect.Array || valVal.Kind() == reflect.Slice
|
||||
}
|
|
@ -0,0 +1,298 @@
|
|||
package squirrel
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/lann/builder"
|
||||
)
|
||||
|
||||
type insertData struct {
|
||||
PlaceholderFormat PlaceholderFormat
|
||||
RunWith BaseRunner
|
||||
Prefixes []Sqlizer
|
||||
StatementKeyword string
|
||||
Options []string
|
||||
Into string
|
||||
Columns []string
|
||||
Values [][]interface{}
|
||||
Suffixes []Sqlizer
|
||||
Select *SelectBuilder
|
||||
}
|
||||
|
||||
func (d *insertData) Exec() (sql.Result, error) {
|
||||
if d.RunWith == nil {
|
||||
return nil, RunnerNotSet
|
||||
}
|
||||
return ExecWith(d.RunWith, d)
|
||||
}
|
||||
|
||||
func (d *insertData) Query() (*sql.Rows, error) {
|
||||
if d.RunWith == nil {
|
||||
return nil, RunnerNotSet
|
||||
}
|
||||
return QueryWith(d.RunWith, d)
|
||||
}
|
||||
|
||||
func (d *insertData) QueryRow() RowScanner {
|
||||
if d.RunWith == nil {
|
||||
return &Row{err: RunnerNotSet}
|
||||
}
|
||||
queryRower, ok := d.RunWith.(QueryRower)
|
||||
if !ok {
|
||||
return &Row{err: RunnerNotQueryRunner}
|
||||
}
|
||||
return QueryRowWith(queryRower, d)
|
||||
}
|
||||
|
||||
func (d *insertData) ToSql() (sqlStr string, args []interface{}, err error) {
|
||||
if len(d.Into) == 0 {
|
||||
err = errors.New("insert statements must specify a table")
|
||||
return
|
||||
}
|
||||
if len(d.Values) == 0 && d.Select == nil {
|
||||
err = errors.New("insert statements must have at least one set of values or select clause")
|
||||
return
|
||||
}
|
||||
|
||||
sql := &bytes.Buffer{}
|
||||
|
||||
if len(d.Prefixes) > 0 {
|
||||
args, err = appendToSql(d.Prefixes, sql, " ", args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sql.WriteString(" ")
|
||||
}
|
||||
|
||||
if d.StatementKeyword == "" {
|
||||
sql.WriteString("INSERT ")
|
||||
} else {
|
||||
sql.WriteString(d.StatementKeyword)
|
||||
sql.WriteString(" ")
|
||||
}
|
||||
|
||||
if len(d.Options) > 0 {
|
||||
sql.WriteString(strings.Join(d.Options, " "))
|
||||
sql.WriteString(" ")
|
||||
}
|
||||
|
||||
sql.WriteString("INTO ")
|
||||
sql.WriteString(d.Into)
|
||||
sql.WriteString(" ")
|
||||
|
||||
if len(d.Columns) > 0 {
|
||||
sql.WriteString("(")
|
||||
sql.WriteString(strings.Join(d.Columns, ","))
|
||||
sql.WriteString(") ")
|
||||
}
|
||||
|
||||
if d.Select != nil {
|
||||
args, err = d.appendSelectToSQL(sql, args)
|
||||
} else {
|
||||
args, err = d.appendValuesToSQL(sql, args)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(d.Suffixes) > 0 {
|
||||
sql.WriteString(" ")
|
||||
args, err = appendToSql(d.Suffixes, sql, " ", args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
sqlStr, err = d.PlaceholderFormat.ReplacePlaceholders(sql.String())
|
||||
return
|
||||
}
|
||||
|
||||
func (d *insertData) appendValuesToSQL(w io.Writer, args []interface{}) ([]interface{}, error) {
|
||||
if len(d.Values) == 0 {
|
||||
return args, errors.New("values for insert statements are not set")
|
||||
}
|
||||
|
||||
io.WriteString(w, "VALUES ")
|
||||
|
||||
valuesStrings := make([]string, len(d.Values))
|
||||
for r, row := range d.Values {
|
||||
valueStrings := make([]string, len(row))
|
||||
for v, val := range row {
|
||||
if vs, ok := val.(Sqlizer); ok {
|
||||
vsql, vargs, err := vs.ToSql()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
valueStrings[v] = vsql
|
||||
args = append(args, vargs...)
|
||||
} else {
|
||||
valueStrings[v] = "?"
|
||||
args = append(args, val)
|
||||
}
|
||||
}
|
||||
valuesStrings[r] = fmt.Sprintf("(%s)", strings.Join(valueStrings, ","))
|
||||
}
|
||||
|
||||
io.WriteString(w, strings.Join(valuesStrings, ","))
|
||||
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (d *insertData) appendSelectToSQL(w io.Writer, args []interface{}) ([]interface{}, error) {
|
||||
if d.Select == nil {
|
||||
return args, errors.New("select clause for insert statements are not set")
|
||||
}
|
||||
|
||||
selectClause, sArgs, err := d.Select.ToSql()
|
||||
if err != nil {
|
||||
return args, err
|
||||
}
|
||||
|
||||
io.WriteString(w, selectClause)
|
||||
args = append(args, sArgs...)
|
||||
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// Builder
|
||||
|
||||
// InsertBuilder builds SQL INSERT statements.
|
||||
type InsertBuilder builder.Builder
|
||||
|
||||
func init() {
|
||||
builder.Register(InsertBuilder{}, insertData{})
|
||||
}
|
||||
|
||||
// Format methods
|
||||
|
||||
// PlaceholderFormat sets PlaceholderFormat (e.g. Question or Dollar) for the
|
||||
// query.
|
||||
func (b InsertBuilder) PlaceholderFormat(f PlaceholderFormat) InsertBuilder {
|
||||
return builder.Set(b, "PlaceholderFormat", f).(InsertBuilder)
|
||||
}
|
||||
|
||||
// Runner methods
|
||||
|
||||
// RunWith sets a Runner (like database/sql.DB) to be used with e.g. Exec.
|
||||
func (b InsertBuilder) RunWith(runner BaseRunner) InsertBuilder {
|
||||
return setRunWith(b, runner).(InsertBuilder)
|
||||
}
|
||||
|
||||
// Exec builds and Execs the query with the Runner set by RunWith.
|
||||
func (b InsertBuilder) Exec() (sql.Result, error) {
|
||||
data := builder.GetStruct(b).(insertData)
|
||||
return data.Exec()
|
||||
}
|
||||
|
||||
// Query builds and Querys the query with the Runner set by RunWith.
|
||||
func (b InsertBuilder) Query() (*sql.Rows, error) {
|
||||
data := builder.GetStruct(b).(insertData)
|
||||
return data.Query()
|
||||
}
|
||||
|
||||
// QueryRow builds and QueryRows the query with the Runner set by RunWith.
|
||||
func (b InsertBuilder) QueryRow() RowScanner {
|
||||
data := builder.GetStruct(b).(insertData)
|
||||
return data.QueryRow()
|
||||
}
|
||||
|
||||
// Scan is a shortcut for QueryRow().Scan.
|
||||
func (b InsertBuilder) Scan(dest ...interface{}) error {
|
||||
return b.QueryRow().Scan(dest...)
|
||||
}
|
||||
|
||||
// SQL methods
|
||||
|
||||
// ToSql builds the query into a SQL string and bound args.
|
||||
func (b InsertBuilder) ToSql() (string, []interface{}, error) {
|
||||
data := builder.GetStruct(b).(insertData)
|
||||
return data.ToSql()
|
||||
}
|
||||
|
||||
// MustSql builds the query into a SQL string and bound args.
|
||||
// It panics if there are any errors.
|
||||
func (b InsertBuilder) MustSql() (string, []interface{}) {
|
||||
sql, args, err := b.ToSql()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sql, args
|
||||
}
|
||||
|
||||
// Prefix adds an expression to the beginning of the query
|
||||
func (b InsertBuilder) Prefix(sql string, args ...interface{}) InsertBuilder {
|
||||
return b.PrefixExpr(Expr(sql, args...))
|
||||
}
|
||||
|
||||
// PrefixExpr adds an expression to the very beginning of the query
|
||||
func (b InsertBuilder) PrefixExpr(expr Sqlizer) InsertBuilder {
|
||||
return builder.Append(b, "Prefixes", expr).(InsertBuilder)
|
||||
}
|
||||
|
||||
// Options adds keyword options before the INTO clause of the query.
|
||||
func (b InsertBuilder) Options(options ...string) InsertBuilder {
|
||||
return builder.Extend(b, "Options", options).(InsertBuilder)
|
||||
}
|
||||
|
||||
// Into sets the INTO clause of the query.
|
||||
func (b InsertBuilder) Into(from string) InsertBuilder {
|
||||
return builder.Set(b, "Into", from).(InsertBuilder)
|
||||
}
|
||||
|
||||
// Columns adds insert columns to the query.
|
||||
func (b InsertBuilder) Columns(columns ...string) InsertBuilder {
|
||||
return builder.Extend(b, "Columns", columns).(InsertBuilder)
|
||||
}
|
||||
|
||||
// Values adds a single row's values to the query.
|
||||
func (b InsertBuilder) Values(values ...interface{}) InsertBuilder {
|
||||
return builder.Append(b, "Values", values).(InsertBuilder)
|
||||
}
|
||||
|
||||
// Suffix adds an expression to the end of the query
|
||||
func (b InsertBuilder) Suffix(sql string, args ...interface{}) InsertBuilder {
|
||||
return b.SuffixExpr(Expr(sql, args...))
|
||||
}
|
||||
|
||||
// SuffixExpr adds an expression to the end of the query
|
||||
func (b InsertBuilder) SuffixExpr(expr Sqlizer) InsertBuilder {
|
||||
return builder.Append(b, "Suffixes", expr).(InsertBuilder)
|
||||
}
|
||||
|
||||
// SetMap set columns and values for insert builder from a map of column name and value
|
||||
// note that it will reset all previous columns and values was set if any
|
||||
func (b InsertBuilder) SetMap(clauses map[string]interface{}) InsertBuilder {
|
||||
// Keep the columns in a consistent order by sorting the column key string.
|
||||
cols := make([]string, 0, len(clauses))
|
||||
for col := range clauses {
|
||||
cols = append(cols, col)
|
||||
}
|
||||
sort.Strings(cols)
|
||||
|
||||
vals := make([]interface{}, 0, len(clauses))
|
||||
for _, col := range cols {
|
||||
vals = append(vals, clauses[col])
|
||||
}
|
||||
|
||||
b = builder.Set(b, "Columns", cols).(InsertBuilder)
|
||||
b = builder.Set(b, "Values", [][]interface{}{vals}).(InsertBuilder)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// Select set Select clause for insert query
|
||||
// If Values and Select are used, then Select has higher priority
|
||||
func (b InsertBuilder) Select(sb SelectBuilder) InsertBuilder {
|
||||
return builder.Set(b, "Select", &sb).(InsertBuilder)
|
||||
}
|
||||
|
||||
func (b InsertBuilder) statementKeyword(keyword string) InsertBuilder {
|
||||
return builder.Set(b, "StatementKeyword", keyword).(InsertBuilder)
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
// +build go1.8
|
||||
|
||||
package squirrel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/lann/builder"
|
||||
)
|
||||
|
||||
func (d *insertData) ExecContext(ctx context.Context) (sql.Result, error) {
|
||||
if d.RunWith == nil {
|
||||
return nil, RunnerNotSet
|
||||
}
|
||||
ctxRunner, ok := d.RunWith.(ExecerContext)
|
||||
if !ok {
|
||||
return nil, NoContextSupport
|
||||
}
|
||||
return ExecContextWith(ctx, ctxRunner, d)
|
||||
}
|
||||
|
||||
func (d *insertData) QueryContext(ctx context.Context) (*sql.Rows, error) {
|
||||
if d.RunWith == nil {
|
||||
return nil, RunnerNotSet
|
||||
}
|
||||
ctxRunner, ok := d.RunWith.(QueryerContext)
|
||||
if !ok {
|
||||
return nil, NoContextSupport
|
||||
}
|
||||
return QueryContextWith(ctx, ctxRunner, d)
|
||||
}
|
||||
|
||||
func (d *insertData) QueryRowContext(ctx context.Context) RowScanner {
|
||||
if d.RunWith == nil {
|
||||
return &Row{err: RunnerNotSet}
|
||||
}
|
||||
queryRower, ok := d.RunWith.(QueryRowerContext)
|
||||
if !ok {
|
||||
if _, ok := d.RunWith.(QueryerContext); !ok {
|
||||
return &Row{err: RunnerNotQueryRunner}
|
||||
}
|
||||
return &Row{err: NoContextSupport}
|
||||
}
|
||||
return QueryRowContextWith(ctx, queryRower, d)
|
||||
}
|
||||
|
||||
// ExecContext builds and ExecContexts the query with the Runner set by RunWith.
|
||||
func (b InsertBuilder) ExecContext(ctx context.Context) (sql.Result, error) {
|
||||
data := builder.GetStruct(b).(insertData)
|
||||
return data.ExecContext(ctx)
|
||||
}
|
||||
|
||||
// QueryContext builds and QueryContexts the query with the Runner set by RunWith.
|
||||
func (b InsertBuilder) QueryContext(ctx context.Context) (*sql.Rows, error) {
|
||||
data := builder.GetStruct(b).(insertData)
|
||||
return data.QueryContext(ctx)
|
||||
}
|
||||
|
||||
// QueryRowContext builds and QueryRowContexts the query with the Runner set by RunWith.
|
||||
func (b InsertBuilder) QueryRowContext(ctx context.Context) RowScanner {
|
||||
data := builder.GetStruct(b).(insertData)
|
||||
return data.QueryRowContext(ctx)
|
||||
}
|
||||
|
||||
// ScanContext is a shortcut for QueryRowContext().Scan.
|
||||
func (b InsertBuilder) ScanContext(ctx context.Context, dest ...interface{}) error {
|
||||
return b.QueryRowContext(ctx).Scan(dest...)
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package squirrel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type part struct {
|
||||
pred interface{}
|
||||
args []interface{}
|
||||
}
|
||||
|
||||
func newPart(pred interface{}, args ...interface{}) Sqlizer {
|
||||
return &part{pred, args}
|
||||
}
|
||||
|
||||
func (p part) ToSql() (sql string, args []interface{}, err error) {
|
||||
switch pred := p.pred.(type) {
|
||||
case nil:
|
||||
// no-op
|
||||
case Sqlizer:
|
||||
sql, args, err = nestedToSql(pred)
|
||||
case string:
|
||||
sql = pred
|
||||
args = p.args
|
||||
default:
|
||||
err = fmt.Errorf("expected string or Sqlizer, not %T", pred)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func nestedToSql(s Sqlizer) (string, []interface{}, error) {
|
||||
if raw, ok := s.(rawSqlizer); ok {
|
||||
return raw.toSqlRaw()
|
||||
} else {
|
||||
return s.ToSql()
|
||||
}
|
||||
}
|
||||
|
||||
func appendToSql(parts []Sqlizer, w io.Writer, sep string, args []interface{}) ([]interface{}, error) {
|
||||
for i, p := range parts {
|
||||
partSql, partArgs, err := nestedToSql(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(partSql) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if i > 0 {
|
||||
_, err := io.WriteString(w, sep)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
_, err = io.WriteString(w, partSql)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args = append(args, partArgs...)
|
||||
}
|
||||
return args, nil
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
package squirrel
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// PlaceholderFormat is the interface that wraps the ReplacePlaceholders method.
|
||||
//
|
||||
// ReplacePlaceholders takes a SQL statement and replaces each question mark
|
||||
// placeholder with a (possibly different) SQL placeholder.
|
||||
type PlaceholderFormat interface {
|
||||
ReplacePlaceholders(sql string) (string, error)
|
||||
}
|
||||
|
||||
type placeholderDebugger interface {
|
||||
debugPlaceholder() string
|
||||
}
|
||||
|
||||
var (
|
||||
// Question is a PlaceholderFormat instance that leaves placeholders as
|
||||
// question marks.
|
||||
Question = questionFormat{}
|
||||
|
||||
// Dollar is a PlaceholderFormat instance that replaces placeholders with
|
||||
// dollar-prefixed positional placeholders (e.g. $1, $2, $3).
|
||||
Dollar = dollarFormat{}
|
||||
|
||||
// Colon is a PlaceholderFormat instance that replaces placeholders with
|
||||
// colon-prefixed positional placeholders (e.g. :1, :2, :3).
|
||||
Colon = colonFormat{}
|
||||
|
||||
// AtP is a PlaceholderFormat instance that replaces placeholders with
|
||||
// "@p"-prefixed positional placeholders (e.g. @p1, @p2, @p3).
|
||||
AtP = atpFormat{}
|
||||
)
|
||||
|
||||
type questionFormat struct{}
|
||||
|
||||
func (questionFormat) ReplacePlaceholders(sql string) (string, error) {
|
||||
return sql, nil
|
||||
}
|
||||
|
||||
func (questionFormat) debugPlaceholder() string {
|
||||
return "?"
|
||||
}
|
||||
|
||||
type dollarFormat struct{}
|
||||
|
||||
func (dollarFormat) ReplacePlaceholders(sql string) (string, error) {
|
||||
return replacePositionalPlaceholders(sql, "$")
|
||||
}
|
||||
|
||||
func (dollarFormat) debugPlaceholder() string {
|
||||
return "$"
|
||||
}
|
||||
|
||||
type colonFormat struct{}
|
||||
|
||||
func (colonFormat) ReplacePlaceholders(sql string) (string, error) {
|
||||
return replacePositionalPlaceholders(sql, ":")
|
||||
}
|
||||
|
||||
func (colonFormat) debugPlaceholder() string {
|
||||
return ":"
|
||||
}
|
||||
|
||||
type atpFormat struct{}
|
||||
|
||||
func (atpFormat) ReplacePlaceholders(sql string) (string, error) {
|
||||
return replacePositionalPlaceholders(sql, "@p")
|
||||
}
|
||||
|
||||
func (atpFormat) debugPlaceholder() string {
|
||||
return "@p"
|
||||
}
|
||||
|
||||
// Placeholders returns a string with count ? placeholders joined with commas.
|
||||
func Placeholders(count int) string {
|
||||
if count < 1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return strings.Repeat(",?", count)[1:]
|
||||
}
|
||||
|
||||
func replacePositionalPlaceholders(sql, prefix string) (string, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
i := 0
|
||||
for {
|
||||
p := strings.Index(sql, "?")
|
||||
if p == -1 {
|
||||
break
|
||||
}
|
||||
|
||||
if len(sql[p:]) > 1 && sql[p:p+2] == "??" { // escape ?? => ?
|
||||
buf.WriteString(sql[:p])
|
||||
buf.WriteString("?")
|
||||
if len(sql[p:]) == 1 {
|
||||
break
|
||||
}
|
||||
sql = sql[p+2:]
|
||||
} else {
|
||||
i++
|
||||
buf.WriteString(sql[:p])
|
||||
fmt.Fprintf(buf, "%s%d", prefix, i)
|
||||
sql = sql[p+1:]
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString(sql)
|
||||
return buf.String(), nil
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package squirrel
|
||||
|
||||
// RowScanner is the interface that wraps the Scan method.
|
||||
//
|
||||
// Scan behaves like database/sql.Row.Scan.
|
||||
type RowScanner interface {
|
||||
Scan(...interface{}) error
|
||||
}
|
||||
|
||||
// Row wraps database/sql.Row to let squirrel return new errors on Scan.
|
||||
type Row struct {
|
||||
RowScanner
|
||||
err error
|
||||
}
|
||||
|
||||
// Scan returns Row.err or calls RowScanner.Scan.
|
||||
func (r *Row) Scan(dest ...interface{}) error {
|
||||
if r.err != nil {
|
||||
return r.err
|
||||
}
|
||||
return r.RowScanner.Scan(dest...)
|
||||
}
|
|
@ -0,0 +1,403 @@
|
|||
package squirrel
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/lann/builder"
|
||||
)
|
||||
|
||||
type selectData struct {
|
||||
PlaceholderFormat PlaceholderFormat
|
||||
RunWith BaseRunner
|
||||
Prefixes []Sqlizer
|
||||
Options []string
|
||||
Columns []Sqlizer
|
||||
From Sqlizer
|
||||
Joins []Sqlizer
|
||||
WhereParts []Sqlizer
|
||||
GroupBys []string
|
||||
HavingParts []Sqlizer
|
||||
OrderByParts []Sqlizer
|
||||
Limit string
|
||||
Offset string
|
||||
Suffixes []Sqlizer
|
||||
}
|
||||
|
||||
func (d *selectData) Exec() (sql.Result, error) {
|
||||
if d.RunWith == nil {
|
||||
return nil, RunnerNotSet
|
||||
}
|
||||
return ExecWith(d.RunWith, d)
|
||||
}
|
||||
|
||||
func (d *selectData) Query() (*sql.Rows, error) {
|
||||
if d.RunWith == nil {
|
||||
return nil, RunnerNotSet
|
||||
}
|
||||
return QueryWith(d.RunWith, d)
|
||||
}
|
||||
|
||||
func (d *selectData) QueryRow() RowScanner {
|
||||
if d.RunWith == nil {
|
||||
return &Row{err: RunnerNotSet}
|
||||
}
|
||||
queryRower, ok := d.RunWith.(QueryRower)
|
||||
if !ok {
|
||||
return &Row{err: RunnerNotQueryRunner}
|
||||
}
|
||||
return QueryRowWith(queryRower, d)
|
||||
}
|
||||
|
||||
func (d *selectData) ToSql() (sqlStr string, args []interface{}, err error) {
|
||||
sqlStr, args, err = d.toSqlRaw()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sqlStr, err = d.PlaceholderFormat.ReplacePlaceholders(sqlStr)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *selectData) toSqlRaw() (sqlStr string, args []interface{}, err error) {
|
||||
if len(d.Columns) == 0 {
|
||||
err = fmt.Errorf("select statements must have at least one result column")
|
||||
return
|
||||
}
|
||||
|
||||
sql := &bytes.Buffer{}
|
||||
|
||||
if len(d.Prefixes) > 0 {
|
||||
args, err = appendToSql(d.Prefixes, sql, " ", args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sql.WriteString(" ")
|
||||
}
|
||||
|
||||
sql.WriteString("SELECT ")
|
||||
|
||||
if len(d.Options) > 0 {
|
||||
sql.WriteString(strings.Join(d.Options, " "))
|
||||
sql.WriteString(" ")
|
||||
}
|
||||
|
||||
if len(d.Columns) > 0 {
|
||||
args, err = appendToSql(d.Columns, sql, ", ", args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if d.From != nil {
|
||||
sql.WriteString(" FROM ")
|
||||
args, err = appendToSql([]Sqlizer{d.From}, sql, "", args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(d.Joins) > 0 {
|
||||
sql.WriteString(" ")
|
||||
args, err = appendToSql(d.Joins, sql, " ", args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(d.WhereParts) > 0 {
|
||||
sql.WriteString(" WHERE ")
|
||||
args, err = appendToSql(d.WhereParts, sql, " AND ", args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(d.GroupBys) > 0 {
|
||||
sql.WriteString(" GROUP BY ")
|
||||
sql.WriteString(strings.Join(d.GroupBys, ", "))
|
||||
}
|
||||
|
||||
if len(d.HavingParts) > 0 {
|
||||
sql.WriteString(" HAVING ")
|
||||
args, err = appendToSql(d.HavingParts, sql, " AND ", args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(d.OrderByParts) > 0 {
|
||||
sql.WriteString(" ORDER BY ")
|
||||
args, err = appendToSql(d.OrderByParts, sql, ", ", args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(d.Limit) > 0 {
|
||||
sql.WriteString(" LIMIT ")
|
||||
sql.WriteString(d.Limit)
|
||||
}
|
||||
|
||||
if len(d.Offset) > 0 {
|
||||
sql.WriteString(" OFFSET ")
|
||||
sql.WriteString(d.Offset)
|
||||
}
|
||||
|
||||
if len(d.Suffixes) > 0 {
|
||||
sql.WriteString(" ")
|
||||
|
||||
args, err = appendToSql(d.Suffixes, sql, " ", args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
sqlStr = sql.String()
|
||||
return
|
||||
}
|
||||
|
||||
// Builder
|
||||
|
||||
// SelectBuilder builds SQL SELECT statements.
|
||||
type SelectBuilder builder.Builder
|
||||
|
||||
func init() {
|
||||
builder.Register(SelectBuilder{}, selectData{})
|
||||
}
|
||||
|
||||
// Format methods
|
||||
|
||||
// PlaceholderFormat sets PlaceholderFormat (e.g. Question or Dollar) for the
|
||||
// query.
|
||||
func (b SelectBuilder) PlaceholderFormat(f PlaceholderFormat) SelectBuilder {
|
||||
return builder.Set(b, "PlaceholderFormat", f).(SelectBuilder)
|
||||
}
|
||||
|
||||
// Runner methods
|
||||
|
||||
// RunWith sets a Runner (like database/sql.DB) to be used with e.g. Exec.
|
||||
// For most cases runner will be a database connection.
|
||||
//
|
||||
// Internally we use this to mock out the database connection for testing.
|
||||
func (b SelectBuilder) RunWith(runner BaseRunner) SelectBuilder {
|
||||
return setRunWith(b, runner).(SelectBuilder)
|
||||
}
|
||||
|
||||
// Exec builds and Execs the query with the Runner set by RunWith.
|
||||
func (b SelectBuilder) Exec() (sql.Result, error) {
|
||||
data := builder.GetStruct(b).(selectData)
|
||||
return data.Exec()
|
||||
}
|
||||
|
||||
// Query builds and Querys the query with the Runner set by RunWith.
|
||||
func (b SelectBuilder) Query() (*sql.Rows, error) {
|
||||
data := builder.GetStruct(b).(selectData)
|
||||
return data.Query()
|
||||
}
|
||||
|
||||
// QueryRow builds and QueryRows the query with the Runner set by RunWith.
|
||||
func (b SelectBuilder) QueryRow() RowScanner {
|
||||
data := builder.GetStruct(b).(selectData)
|
||||
return data.QueryRow()
|
||||
}
|
||||
|
||||
// Scan is a shortcut for QueryRow().Scan.
|
||||
func (b SelectBuilder) Scan(dest ...interface{}) error {
|
||||
return b.QueryRow().Scan(dest...)
|
||||
}
|
||||
|
||||
// SQL methods
|
||||
|
||||
// ToSql builds the query into a SQL string and bound args.
|
||||
func (b SelectBuilder) ToSql() (string, []interface{}, error) {
|
||||
data := builder.GetStruct(b).(selectData)
|
||||
return data.ToSql()
|
||||
}
|
||||
|
||||
func (b SelectBuilder) toSqlRaw() (string, []interface{}, error) {
|
||||
data := builder.GetStruct(b).(selectData)
|
||||
return data.toSqlRaw()
|
||||
}
|
||||
|
||||
// MustSql builds the query into a SQL string and bound args.
|
||||
// It panics if there are any errors.
|
||||
func (b SelectBuilder) MustSql() (string, []interface{}) {
|
||||
sql, args, err := b.ToSql()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sql, args
|
||||
}
|
||||
|
||||
// Prefix adds an expression to the beginning of the query
|
||||
func (b SelectBuilder) Prefix(sql string, args ...interface{}) SelectBuilder {
|
||||
return b.PrefixExpr(Expr(sql, args...))
|
||||
}
|
||||
|
||||
// PrefixExpr adds an expression to the very beginning of the query
|
||||
func (b SelectBuilder) PrefixExpr(expr Sqlizer) SelectBuilder {
|
||||
return builder.Append(b, "Prefixes", expr).(SelectBuilder)
|
||||
}
|
||||
|
||||
// Distinct adds a DISTINCT clause to the query.
|
||||
func (b SelectBuilder) Distinct() SelectBuilder {
|
||||
return b.Options("DISTINCT")
|
||||
}
|
||||
|
||||
// Options adds select option to the query
|
||||
func (b SelectBuilder) Options(options ...string) SelectBuilder {
|
||||
return builder.Extend(b, "Options", options).(SelectBuilder)
|
||||
}
|
||||
|
||||
// Columns adds result columns to the query.
|
||||
func (b SelectBuilder) Columns(columns ...string) SelectBuilder {
|
||||
parts := make([]interface{}, 0, len(columns))
|
||||
for _, str := range columns {
|
||||
parts = append(parts, newPart(str))
|
||||
}
|
||||
return builder.Extend(b, "Columns", parts).(SelectBuilder)
|
||||
}
|
||||
|
||||
// RemoveColumns remove all columns from query.
|
||||
// Must add a new column with Column or Columns methods, otherwise
|
||||
// return a error.
|
||||
func (b SelectBuilder) RemoveColumns() SelectBuilder {
|
||||
return builder.Delete(b, "Columns").(SelectBuilder)
|
||||
}
|
||||
|
||||
// Column adds a result column to the query.
|
||||
// Unlike Columns, Column accepts args which will be bound to placeholders in
|
||||
// the columns string, for example:
|
||||
// Column("IF(col IN ("+squirrel.Placeholders(3)+"), 1, 0) as col", 1, 2, 3)
|
||||
func (b SelectBuilder) Column(column interface{}, args ...interface{}) SelectBuilder {
|
||||
return builder.Append(b, "Columns", newPart(column, args...)).(SelectBuilder)
|
||||
}
|
||||
|
||||
// From sets the FROM clause of the query.
|
||||
func (b SelectBuilder) From(from string) SelectBuilder {
|
||||
return builder.Set(b, "From", newPart(from)).(SelectBuilder)
|
||||
}
|
||||
|
||||
// FromSelect sets a subquery into the FROM clause of the query.
|
||||
func (b SelectBuilder) FromSelect(from SelectBuilder, alias string) SelectBuilder {
|
||||
// Prevent misnumbered parameters in nested selects (#183).
|
||||
from = from.PlaceholderFormat(Question)
|
||||
return builder.Set(b, "From", Alias(from, alias)).(SelectBuilder)
|
||||
}
|
||||
|
||||
// JoinClause adds a join clause to the query.
|
||||
func (b SelectBuilder) JoinClause(pred interface{}, args ...interface{}) SelectBuilder {
|
||||
return builder.Append(b, "Joins", newPart(pred, args...)).(SelectBuilder)
|
||||
}
|
||||
|
||||
// Join adds a JOIN clause to the query.
|
||||
func (b SelectBuilder) Join(join string, rest ...interface{}) SelectBuilder {
|
||||
return b.JoinClause("JOIN "+join, rest...)
|
||||
}
|
||||
|
||||
// LeftJoin adds a LEFT JOIN clause to the query.
|
||||
func (b SelectBuilder) LeftJoin(join string, rest ...interface{}) SelectBuilder {
|
||||
return b.JoinClause("LEFT JOIN "+join, rest...)
|
||||
}
|
||||
|
||||
// RightJoin adds a RIGHT JOIN clause to the query.
|
||||
func (b SelectBuilder) RightJoin(join string, rest ...interface{}) SelectBuilder {
|
||||
return b.JoinClause("RIGHT JOIN "+join, rest...)
|
||||
}
|
||||
|
||||
// InnerJoin adds a INNER JOIN clause to the query.
|
||||
func (b SelectBuilder) InnerJoin(join string, rest ...interface{}) SelectBuilder {
|
||||
return b.JoinClause("INNER JOIN "+join, rest...)
|
||||
}
|
||||
|
||||
// CrossJoin adds a CROSS JOIN clause to the query.
|
||||
func (b SelectBuilder) CrossJoin(join string, rest ...interface{}) SelectBuilder {
|
||||
return b.JoinClause("CROSS JOIN "+join, rest...)
|
||||
}
|
||||
|
||||
// Where adds an expression to the WHERE clause of the query.
|
||||
//
|
||||
// Expressions are ANDed together in the generated SQL.
|
||||
//
|
||||
// Where accepts several types for its pred argument:
|
||||
//
|
||||
// nil OR "" - ignored.
|
||||
//
|
||||
// string - SQL expression.
|
||||
// If the expression has SQL placeholders then a set of arguments must be passed
|
||||
// as well, one for each placeholder.
|
||||
//
|
||||
// map[string]interface{} OR Eq - map of SQL expressions to values. Each key is
|
||||
// transformed into an expression like "<key> = ?", with the corresponding value
|
||||
// bound to the placeholder. If the value is nil, the expression will be "<key>
|
||||
// IS NULL". If the value is an array or slice, the expression will be "<key> IN
|
||||
// (?,?,...)", with one placeholder for each item in the value. These expressions
|
||||
// are ANDed together.
|
||||
//
|
||||
// Where will panic if pred isn't any of the above types.
|
||||
func (b SelectBuilder) Where(pred interface{}, args ...interface{}) SelectBuilder {
|
||||
if pred == nil || pred == "" {
|
||||
return b
|
||||
}
|
||||
return builder.Append(b, "WhereParts", newWherePart(pred, args...)).(SelectBuilder)
|
||||
}
|
||||
|
||||
// GroupBy adds GROUP BY expressions to the query.
|
||||
func (b SelectBuilder) GroupBy(groupBys ...string) SelectBuilder {
|
||||
return builder.Extend(b, "GroupBys", groupBys).(SelectBuilder)
|
||||
}
|
||||
|
||||
// Having adds an expression to the HAVING clause of the query.
|
||||
//
|
||||
// See Where.
|
||||
func (b SelectBuilder) Having(pred interface{}, rest ...interface{}) SelectBuilder {
|
||||
return builder.Append(b, "HavingParts", newWherePart(pred, rest...)).(SelectBuilder)
|
||||
}
|
||||
|
||||
// OrderByClause adds ORDER BY clause to the query.
|
||||
func (b SelectBuilder) OrderByClause(pred interface{}, args ...interface{}) SelectBuilder {
|
||||
return builder.Append(b, "OrderByParts", newPart(pred, args...)).(SelectBuilder)
|
||||
}
|
||||
|
||||
// OrderBy adds ORDER BY expressions to the query.
|
||||
func (b SelectBuilder) OrderBy(orderBys ...string) SelectBuilder {
|
||||
for _, orderBy := range orderBys {
|
||||
b = b.OrderByClause(orderBy)
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// Limit sets a LIMIT clause on the query.
|
||||
func (b SelectBuilder) Limit(limit uint64) SelectBuilder {
|
||||
return builder.Set(b, "Limit", fmt.Sprintf("%d", limit)).(SelectBuilder)
|
||||
}
|
||||
|
||||
// Limit ALL allows to access all records with limit
|
||||
func (b SelectBuilder) RemoveLimit() SelectBuilder {
|
||||
return builder.Delete(b, "Limit").(SelectBuilder)
|
||||
}
|
||||
|
||||
// Offset sets a OFFSET clause on the query.
|
||||
func (b SelectBuilder) Offset(offset uint64) SelectBuilder {
|
||||
return builder.Set(b, "Offset", fmt.Sprintf("%d", offset)).(SelectBuilder)
|
||||
}
|
||||
|
||||
// RemoveOffset removes OFFSET clause.
|
||||
func (b SelectBuilder) RemoveOffset() SelectBuilder {
|
||||
return builder.Delete(b, "Offset").(SelectBuilder)
|
||||
}
|
||||
|
||||
// Suffix adds an expression to the end of the query
|
||||
func (b SelectBuilder) Suffix(sql string, args ...interface{}) SelectBuilder {
|
||||
return b.SuffixExpr(Expr(sql, args...))
|
||||
}
|
||||
|
||||
// SuffixExpr adds an expression to the end of the query
|
||||
func (b SelectBuilder) SuffixExpr(expr Sqlizer) SelectBuilder {
|
||||
return builder.Append(b, "Suffixes", expr).(SelectBuilder)
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
// +build go1.8
|
||||
|
||||
package squirrel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/lann/builder"
|
||||
)
|
||||
|
||||
func (d *selectData) ExecContext(ctx context.Context) (sql.Result, error) {
|
||||
if d.RunWith == nil {
|
||||
return nil, RunnerNotSet
|
||||
}
|
||||
ctxRunner, ok := d.RunWith.(ExecerContext)
|
||||
if !ok {
|
||||
return nil, NoContextSupport
|
||||
}
|
||||
return ExecContextWith(ctx, ctxRunner, d)
|
||||
}
|
||||
|
||||
func (d *selectData) QueryContext(ctx context.Context) (*sql.Rows, error) {
|
||||
if d.RunWith == nil {
|
||||
return nil, RunnerNotSet
|
||||
}
|
||||
ctxRunner, ok := d.RunWith.(QueryerContext)
|
||||
if !ok {
|
||||
return nil, NoContextSupport
|
||||
}
|
||||
return QueryContextWith(ctx, ctxRunner, d)
|
||||
}
|
||||
|
||||
func (d *selectData) QueryRowContext(ctx context.Context) RowScanner {
|
||||
if d.RunWith == nil {
|
||||
return &Row{err: RunnerNotSet}
|
||||
}
|
||||
queryRower, ok := d.RunWith.(QueryRowerContext)
|
||||
if !ok {
|
||||
if _, ok := d.RunWith.(QueryerContext); !ok {
|
||||
return &Row{err: RunnerNotQueryRunner}
|
||||
}
|
||||
return &Row{err: NoContextSupport}
|
||||
}
|
||||
return QueryRowContextWith(ctx, queryRower, d)
|
||||
}
|
||||
|
||||
// ExecContext builds and ExecContexts the query with the Runner set by RunWith.
|
||||
func (b SelectBuilder) ExecContext(ctx context.Context) (sql.Result, error) {
|
||||
data := builder.GetStruct(b).(selectData)
|
||||
return data.ExecContext(ctx)
|
||||
}
|
||||
|
||||
// QueryContext builds and QueryContexts the query with the Runner set by RunWith.
|
||||
func (b SelectBuilder) QueryContext(ctx context.Context) (*sql.Rows, error) {
|
||||
data := builder.GetStruct(b).(selectData)
|
||||
return data.QueryContext(ctx)
|
||||
}
|
||||
|
||||
// QueryRowContext builds and QueryRowContexts the query with the Runner set by RunWith.
|
||||
func (b SelectBuilder) QueryRowContext(ctx context.Context) RowScanner {
|
||||
data := builder.GetStruct(b).(selectData)
|
||||
return data.QueryRowContext(ctx)
|
||||
}
|
||||
|
||||
// ScanContext is a shortcut for QueryRowContext().Scan.
|
||||
func (b SelectBuilder) ScanContext(ctx context.Context, dest ...interface{}) error {
|
||||
return b.QueryRowContext(ctx).Scan(dest...)
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
// Package squirrel provides a fluent SQL generator.
|
||||
//
|
||||
// See https://github.com/Masterminds/squirrel for examples.
|
||||
package squirrel
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/lann/builder"
|
||||
)
|
||||
|
||||
// Sqlizer is the interface that wraps the ToSql method.
|
||||
//
|
||||
// ToSql returns a SQL representation of the Sqlizer, along with a slice of args
|
||||
// as passed to e.g. database/sql.Exec. It can also return an error.
|
||||
type Sqlizer interface {
|
||||
ToSql() (string, []interface{}, error)
|
||||
}
|
||||
|
||||
// rawSqlizer is expected to do what Sqlizer does, but without finalizing placeholders.
|
||||
// This is useful for nested queries.
|
||||
type rawSqlizer interface {
|
||||
toSqlRaw() (string, []interface{}, error)
|
||||
}
|
||||
|
||||
// Execer is the interface that wraps the Exec method.
|
||||
//
|
||||
// Exec executes the given query as implemented by database/sql.Exec.
|
||||
type Execer interface {
|
||||
Exec(query string, args ...interface{}) (sql.Result, error)
|
||||
}
|
||||
|
||||
// Queryer is the interface that wraps the Query method.
|
||||
//
|
||||
// Query executes the given query as implemented by database/sql.Query.
|
||||
type Queryer interface {
|
||||
Query(query string, args ...interface{}) (*sql.Rows, error)
|
||||
}
|
||||
|
||||
// QueryRower is the interface that wraps the QueryRow method.
|
||||
//
|
||||
// QueryRow executes the given query as implemented by database/sql.QueryRow.
|
||||
type QueryRower interface {
|
||||
QueryRow(query string, args ...interface{}) RowScanner
|
||||
}
|
||||
|
||||
// BaseRunner groups the Execer and Queryer interfaces.
|
||||
type BaseRunner interface {
|
||||
Execer
|
||||
Queryer
|
||||
}
|
||||
|
||||
// Runner groups the Execer, Queryer, and QueryRower interfaces.
|
||||
type Runner interface {
|
||||
Execer
|
||||
Queryer
|
||||
QueryRower
|
||||
}
|
||||
|
||||
// WrapStdSql wraps a type implementing the standard SQL interface with methods that
|
||||
// squirrel expects.
|
||||
func WrapStdSql(stdSql StdSql) Runner {
|
||||
return &stdsqlRunner{stdSql}
|
||||
}
|
||||
|
||||
// StdSql encompasses the standard methods of the *sql.DB type, and other types that
|
||||
// wrap these methods.
|
||||
type StdSql interface {
|
||||
Query(string, ...interface{}) (*sql.Rows, error)
|
||||
QueryRow(string, ...interface{}) *sql.Row
|
||||
Exec(string, ...interface{}) (sql.Result, error)
|
||||
}
|
||||
|
||||
type stdsqlRunner struct {
|
||||
StdSql
|
||||
}
|
||||
|
||||
func (r *stdsqlRunner) QueryRow(query string, args ...interface{}) RowScanner {
|
||||
return r.StdSql.QueryRow(query, args...)
|
||||
}
|
||||
|
||||
func setRunWith(b interface{}, runner BaseRunner) interface{} {
|
||||
switch r := runner.(type) {
|
||||
case StdSqlCtx:
|
||||
runner = WrapStdSqlCtx(r)
|
||||
case StdSql:
|
||||
runner = WrapStdSql(r)
|
||||
}
|
||||
return builder.Set(b, "RunWith", runner)
|
||||
}
|
||||
|
||||
// RunnerNotSet is returned by methods that need a Runner if it isn't set.
|
||||
var RunnerNotSet = fmt.Errorf("cannot run; no Runner set (RunWith)")
|
||||
|
||||
// RunnerNotQueryRunner is returned by QueryRow if the RunWith value doesn't implement QueryRower.
|
||||
var RunnerNotQueryRunner = fmt.Errorf("cannot QueryRow; Runner is not a QueryRower")
|
||||
|
||||
// ExecWith Execs the SQL returned by s with db.
|
||||
func ExecWith(db Execer, s Sqlizer) (res sql.Result, err error) {
|
||||
query, args, err := s.ToSql()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return db.Exec(query, args...)
|
||||
}
|
||||
|
||||
// QueryWith Querys the SQL returned by s with db.
|
||||
func QueryWith(db Queryer, s Sqlizer) (rows *sql.Rows, err error) {
|
||||
query, args, err := s.ToSql()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return db.Query(query, args...)
|
||||
}
|
||||
|
||||
// QueryRowWith QueryRows the SQL returned by s with db.
|
||||
func QueryRowWith(db QueryRower, s Sqlizer) RowScanner {
|
||||
query, args, err := s.ToSql()
|
||||
return &Row{RowScanner: db.QueryRow(query, args...), err: err}
|
||||
}
|
||||
|
||||
// DebugSqlizer calls ToSql on s and shows the approximate SQL to be executed
|
||||
//
|
||||
// If ToSql returns an error, the result of this method will look like:
|
||||
// "[ToSql error: %s]" or "[DebugSqlizer error: %s]"
|
||||
//
|
||||
// IMPORTANT: As its name suggests, this function should only be used for
|
||||
// debugging. While the string result *might* be valid SQL, this function does
|
||||
// not try very hard to ensure it. Additionally, executing the output of this
|
||||
// function with any untrusted user input is certainly insecure.
|
||||
func DebugSqlizer(s Sqlizer) string {
|
||||
sql, args, err := s.ToSql()
|
||||
if err != nil {
|
||||
return fmt.Sprintf("[ToSql error: %s]", err)
|
||||
}
|
||||
|
||||
var placeholder string
|
||||
downCast, ok := s.(placeholderDebugger)
|
||||
if !ok {
|
||||
placeholder = "?"
|
||||
} else {
|
||||
placeholder = downCast.debugPlaceholder()
|
||||
}
|
||||
// TODO: dedupe this with placeholder.go
|
||||
buf := &bytes.Buffer{}
|
||||
i := 0
|
||||
for {
|
||||
p := strings.Index(sql, placeholder)
|
||||
if p == -1 {
|
||||
break
|
||||
}
|
||||
if len(sql[p:]) > 1 && sql[p:p+2] == "??" { // escape ?? => ?
|
||||
buf.WriteString(sql[:p])
|
||||
buf.WriteString("?")
|
||||
if len(sql[p:]) == 1 {
|
||||
break
|
||||
}
|
||||
sql = sql[p+2:]
|
||||
} else {
|
||||
if i+1 > len(args) {
|
||||
return fmt.Sprintf(
|
||||
"[DebugSqlizer error: too many placeholders in %#v for %d args]",
|
||||
sql, len(args))
|
||||
}
|
||||
buf.WriteString(sql[:p])
|
||||
fmt.Fprintf(buf, "'%v'", args[i])
|
||||
// advance our sql string "cursor" beyond the arg we placed
|
||||
sql = sql[p+1:]
|
||||
i++
|
||||
}
|
||||
}
|
||||
if i < len(args) {
|
||||
return fmt.Sprintf(
|
||||
"[DebugSqlizer error: not enough placeholders in %#v for %d args]",
|
||||
sql, len(args))
|
||||
}
|
||||
// "append" any remaning sql that won't need interpolating
|
||||
buf.WriteString(sql)
|
||||
return buf.String()
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
// +build go1.8
|
||||
|
||||
package squirrel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// NoContextSupport is returned if a db doesn't support Context.
|
||||
var NoContextSupport = errors.New("DB does not support Context")
|
||||
|
||||
// ExecerContext is the interface that wraps the ExecContext method.
|
||||
//
|
||||
// Exec executes the given query as implemented by database/sql.ExecContext.
|
||||
type ExecerContext interface {
|
||||
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
|
||||
}
|
||||
|
||||
// QueryerContext is the interface that wraps the QueryContext method.
|
||||
//
|
||||
// QueryContext executes the given query as implemented by database/sql.QueryContext.
|
||||
type QueryerContext interface {
|
||||
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
|
||||
}
|
||||
|
||||
// QueryRowerContext is the interface that wraps the QueryRowContext method.
|
||||
//
|
||||
// QueryRowContext executes the given query as implemented by database/sql.QueryRowContext.
|
||||
type QueryRowerContext interface {
|
||||
QueryRowContext(ctx context.Context, query string, args ...interface{}) RowScanner
|
||||
}
|
||||
|
||||
// RunnerContext groups the Runner interface, along with the Context versions of each of
|
||||
// its methods
|
||||
type RunnerContext interface {
|
||||
Runner
|
||||
QueryerContext
|
||||
QueryRowerContext
|
||||
ExecerContext
|
||||
}
|
||||
|
||||
// WrapStdSqlCtx wraps a type implementing the standard SQL interface plus the context
|
||||
// versions of the methods with methods that squirrel expects.
|
||||
func WrapStdSqlCtx(stdSqlCtx StdSqlCtx) RunnerContext {
|
||||
return &stdsqlCtxRunner{stdSqlCtx}
|
||||
}
|
||||
|
||||
// StdSqlCtx encompasses the standard methods of the *sql.DB type, along with the Context
|
||||
// versions of those methods, and other types that wrap these methods.
|
||||
type StdSqlCtx interface {
|
||||
StdSql
|
||||
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
|
||||
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
|
||||
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
|
||||
}
|
||||
|
||||
type stdsqlCtxRunner struct {
|
||||
StdSqlCtx
|
||||
}
|
||||
|
||||
func (r *stdsqlCtxRunner) QueryRow(query string, args ...interface{}) RowScanner {
|
||||
return r.StdSqlCtx.QueryRow(query, args...)
|
||||
}
|
||||
|
||||
func (r *stdsqlCtxRunner) QueryRowContext(ctx context.Context, query string, args ...interface{}) RowScanner {
|
||||
return r.StdSqlCtx.QueryRowContext(ctx, query, args...)
|
||||
}
|
||||
|
||||
// ExecContextWith ExecContexts the SQL returned by s with db.
|
||||
func ExecContextWith(ctx context.Context, db ExecerContext, s Sqlizer) (res sql.Result, err error) {
|
||||
query, args, err := s.ToSql()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return db.ExecContext(ctx, query, args...)
|
||||
}
|
||||
|
||||
// QueryContextWith QueryContexts the SQL returned by s with db.
|
||||
func QueryContextWith(ctx context.Context, db QueryerContext, s Sqlizer) (rows *sql.Rows, err error) {
|
||||
query, args, err := s.ToSql()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return db.QueryContext(ctx, query, args...)
|
||||
}
|
||||
|
||||
// QueryRowContextWith QueryRowContexts the SQL returned by s with db.
|
||||
func QueryRowContextWith(ctx context.Context, db QueryRowerContext, s Sqlizer) RowScanner {
|
||||
query, args, err := s.ToSql()
|
||||
return &Row{RowScanner: db.QueryRowContext(ctx, query, args...), err: err}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package squirrel
|
||||
|
||||
import "github.com/lann/builder"
|
||||
|
||||
// StatementBuilderType is the type of StatementBuilder.
|
||||
type StatementBuilderType builder.Builder
|
||||
|
||||
// Select returns a SelectBuilder for this StatementBuilderType.
|
||||
func (b StatementBuilderType) Select(columns ...string) SelectBuilder {
|
||||
return SelectBuilder(b).Columns(columns...)
|
||||
}
|
||||
|
||||
// Insert returns a InsertBuilder for this StatementBuilderType.
|
||||
func (b StatementBuilderType) Insert(into string) InsertBuilder {
|
||||
return InsertBuilder(b).Into(into)
|
||||
}
|
||||
|
||||
// Replace returns a InsertBuilder for this StatementBuilderType with the
|
||||
// statement keyword set to "REPLACE".
|
||||
func (b StatementBuilderType) Replace(into string) InsertBuilder {
|
||||
return InsertBuilder(b).statementKeyword("REPLACE").Into(into)
|
||||
}
|
||||
|
||||
// Update returns a UpdateBuilder for this StatementBuilderType.
|
||||
func (b StatementBuilderType) Update(table string) UpdateBuilder {
|
||||
return UpdateBuilder(b).Table(table)
|
||||
}
|
||||
|
||||
// Delete returns a DeleteBuilder for this StatementBuilderType.
|
||||
func (b StatementBuilderType) Delete(from string) DeleteBuilder {
|
||||
return DeleteBuilder(b).From(from)
|
||||
}
|
||||
|
||||
// PlaceholderFormat sets the PlaceholderFormat field for any child builders.
|
||||
func (b StatementBuilderType) PlaceholderFormat(f PlaceholderFormat) StatementBuilderType {
|
||||
return builder.Set(b, "PlaceholderFormat", f).(StatementBuilderType)
|
||||
}
|
||||
|
||||
// RunWith sets the RunWith field for any child builders.
|
||||
func (b StatementBuilderType) RunWith(runner BaseRunner) StatementBuilderType {
|
||||
return setRunWith(b, runner).(StatementBuilderType)
|
||||
}
|
||||
|
||||
// Where adds WHERE expressions to the query.
|
||||
//
|
||||
// See SelectBuilder.Where for more information.
|
||||
func (b StatementBuilderType) Where(pred interface{}, args ...interface{}) StatementBuilderType {
|
||||
return builder.Append(b, "WhereParts", newWherePart(pred, args...)).(StatementBuilderType)
|
||||
}
|
||||
|
||||
// StatementBuilder is a parent builder for other builders, e.g. SelectBuilder.
|
||||
var StatementBuilder = StatementBuilderType(builder.EmptyBuilder).PlaceholderFormat(Question)
|
||||
|
||||
// Select returns a new SelectBuilder, optionally setting some result columns.
|
||||
//
|
||||
// See SelectBuilder.Columns.
|
||||
func Select(columns ...string) SelectBuilder {
|
||||
return StatementBuilder.Select(columns...)
|
||||
}
|
||||
|
||||
// Insert returns a new InsertBuilder with the given table name.
|
||||
//
|
||||
// See InsertBuilder.Into.
|
||||
func Insert(into string) InsertBuilder {
|
||||
return StatementBuilder.Insert(into)
|
||||
}
|
||||
|
||||
// Replace returns a new InsertBuilder with the statement keyword set to
|
||||
// "REPLACE" and with the given table name.
|
||||
//
|
||||
// See InsertBuilder.Into.
|
||||
func Replace(into string) InsertBuilder {
|
||||
return StatementBuilder.Replace(into)
|
||||
}
|
||||
|
||||
// Update returns a new UpdateBuilder with the given table name.
|
||||
//
|
||||
// See UpdateBuilder.Table.
|
||||
func Update(table string) UpdateBuilder {
|
||||
return StatementBuilder.Update(table)
|
||||
}
|
||||
|
||||
// Delete returns a new DeleteBuilder with the given table name.
|
||||
//
|
||||
// See DeleteBuilder.Table.
|
||||
func Delete(from string) DeleteBuilder {
|
||||
return StatementBuilder.Delete(from)
|
||||
}
|
||||
|
||||
// Case returns a new CaseBuilder
|
||||
// "what" represents case value
|
||||
func Case(what ...interface{}) CaseBuilder {
|
||||
b := CaseBuilder(builder.EmptyBuilder)
|
||||
|
||||
switch len(what) {
|
||||
case 0:
|
||||
case 1:
|
||||
b = b.what(what[0])
|
||||
default:
|
||||
b = b.what(newPart(what[0], what[1:]...))
|
||||
|
||||
}
|
||||
return b
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
package squirrel
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Prepareer is the interface that wraps the Prepare method.
|
||||
//
|
||||
// Prepare executes the given query as implemented by database/sql.Prepare.
|
||||
type Preparer interface {
|
||||
Prepare(query string) (*sql.Stmt, error)
|
||||
}
|
||||
|
||||
// DBProxy groups the Execer, Queryer, QueryRower, and Preparer interfaces.
|
||||
type DBProxy interface {
|
||||
Execer
|
||||
Queryer
|
||||
QueryRower
|
||||
Preparer
|
||||
}
|
||||
|
||||
// NOTE: NewStmtCache is defined in stmtcacher_ctx.go (Go >= 1.8) or stmtcacher_noctx.go (Go < 1.8).
|
||||
|
||||
// StmtCache wraps and delegates down to a Preparer type
|
||||
//
|
||||
// It also automatically prepares all statements sent to the underlying Preparer calls
|
||||
// for Exec, Query and QueryRow and caches the returns *sql.Stmt using the provided
|
||||
// query as the key. So that it can be automatically re-used.
|
||||
type StmtCache struct {
|
||||
prep Preparer
|
||||
cache map[string]*sql.Stmt
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// Prepare delegates down to the underlying Preparer and caches the result
|
||||
// using the provided query as a key
|
||||
func (sc *StmtCache) Prepare(query string) (*sql.Stmt, error) {
|
||||
sc.mu.Lock()
|
||||
defer sc.mu.Unlock()
|
||||
|
||||
stmt, ok := sc.cache[query]
|
||||
if ok {
|
||||
return stmt, nil
|
||||
}
|
||||
stmt, err := sc.prep.Prepare(query)
|
||||
if err == nil {
|
||||
sc.cache[query] = stmt
|
||||
}
|
||||
return stmt, err
|
||||
}
|
||||
|
||||
// Exec delegates down to the underlying Preparer using a prepared statement
|
||||
func (sc *StmtCache) Exec(query string, args ...interface{}) (res sql.Result, err error) {
|
||||
stmt, err := sc.Prepare(query)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return stmt.Exec(args...)
|
||||
}
|
||||
|
||||
// Query delegates down to the underlying Preparer using a prepared statement
|
||||
func (sc *StmtCache) Query(query string, args ...interface{}) (rows *sql.Rows, err error) {
|
||||
stmt, err := sc.Prepare(query)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return stmt.Query(args...)
|
||||
}
|
||||
|
||||
// QueryRow delegates down to the underlying Preparer using a prepared statement
|
||||
func (sc *StmtCache) QueryRow(query string, args ...interface{}) RowScanner {
|
||||
stmt, err := sc.Prepare(query)
|
||||
if err != nil {
|
||||
return &Row{err: err}
|
||||
}
|
||||
return stmt.QueryRow(args...)
|
||||
}
|
||||
|
||||
// Clear removes and closes all the currently cached prepared statements
|
||||
func (sc *StmtCache) Clear() (err error) {
|
||||
sc.mu.Lock()
|
||||
defer sc.mu.Unlock()
|
||||
|
||||
for key, stmt := range sc.cache {
|
||||
delete(sc.cache, key)
|
||||
|
||||
if stmt == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if cerr := stmt.Close(); cerr != nil {
|
||||
err = cerr
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("one or more Stmt.Close failed; last error: %v", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type DBProxyBeginner interface {
|
||||
DBProxy
|
||||
Begin() (*sql.Tx, error)
|
||||
}
|
||||
|
||||
type stmtCacheProxy struct {
|
||||
DBProxy
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewStmtCacheProxy(db *sql.DB) DBProxyBeginner {
|
||||
return &stmtCacheProxy{DBProxy: NewStmtCache(db), db: db}
|
||||
}
|
||||
|
||||
func (sp *stmtCacheProxy) Begin() (*sql.Tx, error) {
|
||||
return sp.db.Begin()
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
// +build go1.8
|
||||
|
||||
package squirrel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
// PrepareerContext is the interface that wraps the Prepare and PrepareContext methods.
|
||||
//
|
||||
// Prepare executes the given query as implemented by database/sql.Prepare.
|
||||
// PrepareContext executes the given query as implemented by database/sql.PrepareContext.
|
||||
type PreparerContext interface {
|
||||
Preparer
|
||||
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
|
||||
}
|
||||
|
||||
// DBProxyContext groups the Execer, Queryer, QueryRower and PreparerContext interfaces.
|
||||
type DBProxyContext interface {
|
||||
Execer
|
||||
Queryer
|
||||
QueryRower
|
||||
PreparerContext
|
||||
}
|
||||
|
||||
// NewStmtCache returns a *StmtCache wrapping a PreparerContext that caches Prepared Stmts.
|
||||
//
|
||||
// Stmts are cached based on the string value of their queries.
|
||||
func NewStmtCache(prep PreparerContext) *StmtCache {
|
||||
return &StmtCache{prep: prep, cache: make(map[string]*sql.Stmt)}
|
||||
}
|
||||
|
||||
// NewStmtCacher is deprecated
|
||||
//
|
||||
// Use NewStmtCache instead
|
||||
func NewStmtCacher(prep PreparerContext) DBProxyContext {
|
||||
return NewStmtCache(prep)
|
||||
}
|
||||
|
||||
// PrepareContext delegates down to the underlying PreparerContext and caches the result
|
||||
// using the provided query as a key
|
||||
func (sc *StmtCache) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) {
|
||||
ctxPrep, ok := sc.prep.(PreparerContext)
|
||||
if !ok {
|
||||
return nil, NoContextSupport
|
||||
}
|
||||
sc.mu.Lock()
|
||||
defer sc.mu.Unlock()
|
||||
stmt, ok := sc.cache[query]
|
||||
if ok {
|
||||
return stmt, nil
|
||||
}
|
||||
stmt, err := ctxPrep.PrepareContext(ctx, query)
|
||||
if err == nil {
|
||||
sc.cache[query] = stmt
|
||||
}
|
||||
return stmt, err
|
||||
}
|
||||
|
||||
// ExecContext delegates down to the underlying PreparerContext using a prepared statement
|
||||
func (sc *StmtCache) ExecContext(ctx context.Context, query string, args ...interface{}) (res sql.Result, err error) {
|
||||
stmt, err := sc.PrepareContext(ctx, query)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return stmt.ExecContext(ctx, args...)
|
||||
}
|
||||
|
||||
// QueryContext delegates down to the underlying PreparerContext using a prepared statement
|
||||
func (sc *StmtCache) QueryContext(ctx context.Context, query string, args ...interface{}) (rows *sql.Rows, err error) {
|
||||
stmt, err := sc.PrepareContext(ctx, query)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return stmt.QueryContext(ctx, args...)
|
||||
}
|
||||
|
||||
// QueryRowContext delegates down to the underlying PreparerContext using a prepared statement
|
||||
func (sc *StmtCache) QueryRowContext(ctx context.Context, query string, args ...interface{}) RowScanner {
|
||||
stmt, err := sc.PrepareContext(ctx, query)
|
||||
if err != nil {
|
||||
return &Row{err: err}
|
||||
}
|
||||
return stmt.QueryRowContext(ctx, args...)
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
// +build !go1.8
|
||||
|
||||
package squirrel
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
// NewStmtCacher returns a DBProxy wrapping prep that caches Prepared Stmts.
|
||||
//
|
||||
// Stmts are cached based on the string value of their queries.
|
||||
func NewStmtCache(prep Preparer) *StmtCache {
|
||||
return &StmtCacher{prep: prep, cache: make(map[string]*sql.Stmt)}
|
||||
}
|
||||
|
||||
// NewStmtCacher is deprecated
|
||||
//
|
||||
// Use NewStmtCache instead
|
||||
func NewStmtCacher(prep Preparer) DBProxy {
|
||||
return NewStmtCache(prep)
|
||||
}
|
|
@ -0,0 +1,288 @@
|
|||
package squirrel
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/lann/builder"
|
||||
)
|
||||
|
||||
type updateData struct {
|
||||
PlaceholderFormat PlaceholderFormat
|
||||
RunWith BaseRunner
|
||||
Prefixes []Sqlizer
|
||||
Table string
|
||||
SetClauses []setClause
|
||||
From Sqlizer
|
||||
WhereParts []Sqlizer
|
||||
OrderBys []string
|
||||
Limit string
|
||||
Offset string
|
||||
Suffixes []Sqlizer
|
||||
}
|
||||
|
||||
type setClause struct {
|
||||
column string
|
||||
value interface{}
|
||||
}
|
||||
|
||||
func (d *updateData) Exec() (sql.Result, error) {
|
||||
if d.RunWith == nil {
|
||||
return nil, RunnerNotSet
|
||||
}
|
||||
return ExecWith(d.RunWith, d)
|
||||
}
|
||||
|
||||
func (d *updateData) Query() (*sql.Rows, error) {
|
||||
if d.RunWith == nil {
|
||||
return nil, RunnerNotSet
|
||||
}
|
||||
return QueryWith(d.RunWith, d)
|
||||
}
|
||||
|
||||
func (d *updateData) QueryRow() RowScanner {
|
||||
if d.RunWith == nil {
|
||||
return &Row{err: RunnerNotSet}
|
||||
}
|
||||
queryRower, ok := d.RunWith.(QueryRower)
|
||||
if !ok {
|
||||
return &Row{err: RunnerNotQueryRunner}
|
||||
}
|
||||
return QueryRowWith(queryRower, d)
|
||||
}
|
||||
|
||||
func (d *updateData) ToSql() (sqlStr string, args []interface{}, err error) {
|
||||
if len(d.Table) == 0 {
|
||||
err = fmt.Errorf("update statements must specify a table")
|
||||
return
|
||||
}
|
||||
if len(d.SetClauses) == 0 {
|
||||
err = fmt.Errorf("update statements must have at least one Set clause")
|
||||
return
|
||||
}
|
||||
|
||||
sql := &bytes.Buffer{}
|
||||
|
||||
if len(d.Prefixes) > 0 {
|
||||
args, err = appendToSql(d.Prefixes, sql, " ", args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sql.WriteString(" ")
|
||||
}
|
||||
|
||||
sql.WriteString("UPDATE ")
|
||||
sql.WriteString(d.Table)
|
||||
|
||||
sql.WriteString(" SET ")
|
||||
setSqls := make([]string, len(d.SetClauses))
|
||||
for i, setClause := range d.SetClauses {
|
||||
var valSql string
|
||||
if vs, ok := setClause.value.(Sqlizer); ok {
|
||||
vsql, vargs, err := vs.ToSql()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if _, ok := vs.(SelectBuilder); ok {
|
||||
valSql = fmt.Sprintf("(%s)", vsql)
|
||||
} else {
|
||||
valSql = vsql
|
||||
}
|
||||
args = append(args, vargs...)
|
||||
} else {
|
||||
valSql = "?"
|
||||
args = append(args, setClause.value)
|
||||
}
|
||||
setSqls[i] = fmt.Sprintf("%s = %s", setClause.column, valSql)
|
||||
}
|
||||
sql.WriteString(strings.Join(setSqls, ", "))
|
||||
|
||||
if d.From != nil {
|
||||
sql.WriteString(" FROM ")
|
||||
args, err = appendToSql([]Sqlizer{d.From}, sql, "", args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(d.WhereParts) > 0 {
|
||||
sql.WriteString(" WHERE ")
|
||||
args, err = appendToSql(d.WhereParts, sql, " AND ", args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(d.OrderBys) > 0 {
|
||||
sql.WriteString(" ORDER BY ")
|
||||
sql.WriteString(strings.Join(d.OrderBys, ", "))
|
||||
}
|
||||
|
||||
if len(d.Limit) > 0 {
|
||||
sql.WriteString(" LIMIT ")
|
||||
sql.WriteString(d.Limit)
|
||||
}
|
||||
|
||||
if len(d.Offset) > 0 {
|
||||
sql.WriteString(" OFFSET ")
|
||||
sql.WriteString(d.Offset)
|
||||
}
|
||||
|
||||
if len(d.Suffixes) > 0 {
|
||||
sql.WriteString(" ")
|
||||
args, err = appendToSql(d.Suffixes, sql, " ", args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
sqlStr, err = d.PlaceholderFormat.ReplacePlaceholders(sql.String())
|
||||
return
|
||||
}
|
||||
|
||||
// Builder
|
||||
|
||||
// UpdateBuilder builds SQL UPDATE statements.
|
||||
type UpdateBuilder builder.Builder
|
||||
|
||||
func init() {
|
||||
builder.Register(UpdateBuilder{}, updateData{})
|
||||
}
|
||||
|
||||
// Format methods
|
||||
|
||||
// PlaceholderFormat sets PlaceholderFormat (e.g. Question or Dollar) for the
|
||||
// query.
|
||||
func (b UpdateBuilder) PlaceholderFormat(f PlaceholderFormat) UpdateBuilder {
|
||||
return builder.Set(b, "PlaceholderFormat", f).(UpdateBuilder)
|
||||
}
|
||||
|
||||
// Runner methods
|
||||
|
||||
// RunWith sets a Runner (like database/sql.DB) to be used with e.g. Exec.
|
||||
func (b UpdateBuilder) RunWith(runner BaseRunner) UpdateBuilder {
|
||||
return setRunWith(b, runner).(UpdateBuilder)
|
||||
}
|
||||
|
||||
// Exec builds and Execs the query with the Runner set by RunWith.
|
||||
func (b UpdateBuilder) Exec() (sql.Result, error) {
|
||||
data := builder.GetStruct(b).(updateData)
|
||||
return data.Exec()
|
||||
}
|
||||
|
||||
func (b UpdateBuilder) Query() (*sql.Rows, error) {
|
||||
data := builder.GetStruct(b).(updateData)
|
||||
return data.Query()
|
||||
}
|
||||
|
||||
func (b UpdateBuilder) QueryRow() RowScanner {
|
||||
data := builder.GetStruct(b).(updateData)
|
||||
return data.QueryRow()
|
||||
}
|
||||
|
||||
func (b UpdateBuilder) Scan(dest ...interface{}) error {
|
||||
return b.QueryRow().Scan(dest...)
|
||||
}
|
||||
|
||||
// SQL methods
|
||||
|
||||
// ToSql builds the query into a SQL string and bound args.
|
||||
func (b UpdateBuilder) ToSql() (string, []interface{}, error) {
|
||||
data := builder.GetStruct(b).(updateData)
|
||||
return data.ToSql()
|
||||
}
|
||||
|
||||
// MustSql builds the query into a SQL string and bound args.
|
||||
// It panics if there are any errors.
|
||||
func (b UpdateBuilder) MustSql() (string, []interface{}) {
|
||||
sql, args, err := b.ToSql()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sql, args
|
||||
}
|
||||
|
||||
// Prefix adds an expression to the beginning of the query
|
||||
func (b UpdateBuilder) Prefix(sql string, args ...interface{}) UpdateBuilder {
|
||||
return b.PrefixExpr(Expr(sql, args...))
|
||||
}
|
||||
|
||||
// PrefixExpr adds an expression to the very beginning of the query
|
||||
func (b UpdateBuilder) PrefixExpr(expr Sqlizer) UpdateBuilder {
|
||||
return builder.Append(b, "Prefixes", expr).(UpdateBuilder)
|
||||
}
|
||||
|
||||
// Table sets the table to be updated.
|
||||
func (b UpdateBuilder) Table(table string) UpdateBuilder {
|
||||
return builder.Set(b, "Table", table).(UpdateBuilder)
|
||||
}
|
||||
|
||||
// Set adds SET clauses to the query.
|
||||
func (b UpdateBuilder) Set(column string, value interface{}) UpdateBuilder {
|
||||
return builder.Append(b, "SetClauses", setClause{column: column, value: value}).(UpdateBuilder)
|
||||
}
|
||||
|
||||
// SetMap is a convenience method which calls .Set for each key/value pair in clauses.
|
||||
func (b UpdateBuilder) SetMap(clauses map[string]interface{}) UpdateBuilder {
|
||||
keys := make([]string, len(clauses))
|
||||
i := 0
|
||||
for key := range clauses {
|
||||
keys[i] = key
|
||||
i++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
val, _ := clauses[key]
|
||||
b = b.Set(key, val)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// From adds FROM clause to the query
|
||||
// FROM is valid construct in postgresql only.
|
||||
func (b UpdateBuilder) From(from string) UpdateBuilder {
|
||||
return builder.Set(b, "From", newPart(from)).(UpdateBuilder)
|
||||
}
|
||||
|
||||
// FromSelect sets a subquery into the FROM clause of the query.
|
||||
func (b UpdateBuilder) FromSelect(from SelectBuilder, alias string) UpdateBuilder {
|
||||
// Prevent misnumbered parameters in nested selects (#183).
|
||||
from = from.PlaceholderFormat(Question)
|
||||
return builder.Set(b, "From", Alias(from, alias)).(UpdateBuilder)
|
||||
}
|
||||
|
||||
// Where adds WHERE expressions to the query.
|
||||
//
|
||||
// See SelectBuilder.Where for more information.
|
||||
func (b UpdateBuilder) Where(pred interface{}, args ...interface{}) UpdateBuilder {
|
||||
return builder.Append(b, "WhereParts", newWherePart(pred, args...)).(UpdateBuilder)
|
||||
}
|
||||
|
||||
// OrderBy adds ORDER BY expressions to the query.
|
||||
func (b UpdateBuilder) OrderBy(orderBys ...string) UpdateBuilder {
|
||||
return builder.Extend(b, "OrderBys", orderBys).(UpdateBuilder)
|
||||
}
|
||||
|
||||
// Limit sets a LIMIT clause on the query.
|
||||
func (b UpdateBuilder) Limit(limit uint64) UpdateBuilder {
|
||||
return builder.Set(b, "Limit", fmt.Sprintf("%d", limit)).(UpdateBuilder)
|
||||
}
|
||||
|
||||
// Offset sets a OFFSET clause on the query.
|
||||
func (b UpdateBuilder) Offset(offset uint64) UpdateBuilder {
|
||||
return builder.Set(b, "Offset", fmt.Sprintf("%d", offset)).(UpdateBuilder)
|
||||
}
|
||||
|
||||
// Suffix adds an expression to the end of the query
|
||||
func (b UpdateBuilder) Suffix(sql string, args ...interface{}) UpdateBuilder {
|
||||
return b.SuffixExpr(Expr(sql, args...))
|
||||
}
|
||||
|
||||
// SuffixExpr adds an expression to the end of the query
|
||||
func (b UpdateBuilder) SuffixExpr(expr Sqlizer) UpdateBuilder {
|
||||
return builder.Append(b, "Suffixes", expr).(UpdateBuilder)
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
// +build go1.8
|
||||
|
||||
package squirrel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/lann/builder"
|
||||
)
|
||||
|
||||
func (d *updateData) ExecContext(ctx context.Context) (sql.Result, error) {
|
||||
if d.RunWith == nil {
|
||||
return nil, RunnerNotSet
|
||||
}
|
||||
ctxRunner, ok := d.RunWith.(ExecerContext)
|
||||
if !ok {
|
||||
return nil, NoContextSupport
|
||||
}
|
||||
return ExecContextWith(ctx, ctxRunner, d)
|
||||
}
|
||||
|
||||
func (d *updateData) QueryContext(ctx context.Context) (*sql.Rows, error) {
|
||||
if d.RunWith == nil {
|
||||
return nil, RunnerNotSet
|
||||
}
|
||||
ctxRunner, ok := d.RunWith.(QueryerContext)
|
||||
if !ok {
|
||||
return nil, NoContextSupport
|
||||
}
|
||||
return QueryContextWith(ctx, ctxRunner, d)
|
||||
}
|
||||
|
||||
func (d *updateData) QueryRowContext(ctx context.Context) RowScanner {
|
||||
if d.RunWith == nil {
|
||||
return &Row{err: RunnerNotSet}
|
||||
}
|
||||
queryRower, ok := d.RunWith.(QueryRowerContext)
|
||||
if !ok {
|
||||
if _, ok := d.RunWith.(QueryerContext); !ok {
|
||||
return &Row{err: RunnerNotQueryRunner}
|
||||
}
|
||||
return &Row{err: NoContextSupport}
|
||||
}
|
||||
return QueryRowContextWith(ctx, queryRower, d)
|
||||
}
|
||||
|
||||
// ExecContext builds and ExecContexts the query with the Runner set by RunWith.
|
||||
func (b UpdateBuilder) ExecContext(ctx context.Context) (sql.Result, error) {
|
||||
data := builder.GetStruct(b).(updateData)
|
||||
return data.ExecContext(ctx)
|
||||
}
|
||||
|
||||
// QueryContext builds and QueryContexts the query with the Runner set by RunWith.
|
||||
func (b UpdateBuilder) QueryContext(ctx context.Context) (*sql.Rows, error) {
|
||||
data := builder.GetStruct(b).(updateData)
|
||||
return data.QueryContext(ctx)
|
||||
}
|
||||
|
||||
// QueryRowContext builds and QueryRowContexts the query with the Runner set by RunWith.
|
||||
func (b UpdateBuilder) QueryRowContext(ctx context.Context) RowScanner {
|
||||
data := builder.GetStruct(b).(updateData)
|
||||
return data.QueryRowContext(ctx)
|
||||
}
|
||||
|
||||
// ScanContext is a shortcut for QueryRowContext().Scan.
|
||||
func (b UpdateBuilder) ScanContext(ctx context.Context, dest ...interface{}) error {
|
||||
return b.QueryRowContext(ctx).Scan(dest...)
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package squirrel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type wherePart part
|
||||
|
||||
func newWherePart(pred interface{}, args ...interface{}) Sqlizer {
|
||||
return &wherePart{pred: pred, args: args}
|
||||
}
|
||||
|
||||
func (p wherePart) ToSql() (sql string, args []interface{}, err error) {
|
||||
switch pred := p.pred.(type) {
|
||||
case nil:
|
||||
// no-op
|
||||
case rawSqlizer:
|
||||
return pred.toSqlRaw()
|
||||
case Sqlizer:
|
||||
return pred.ToSql()
|
||||
case map[string]interface{}:
|
||||
return Eq(pred).ToSql()
|
||||
case string:
|
||||
sql = pred
|
||||
args = p.args
|
||||
default:
|
||||
err = fmt.Errorf("expected string-keyed map or string, not %T", pred)
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
sudo: false
|
||||
language: go
|
||||
install: go get -t -v ./...
|
||||
go:
|
||||
- 1.2.x
|
||||
- 1.3.x
|
||||
- 1.4.x
|
||||
- 1.5.x
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- 1.11.x
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (C) 2014 Alec Thomas
|
||||
|
||||
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.
|
|
@ -0,0 +1,709 @@
|
|||
# CONTRIBUTIONS ONLY
|
||||
|
||||
**What does this mean?** I do not have time to fix issues myself. The only way fixes or new features will be added is by people submitting PRs. If you are interested in taking over maintenance and have a history of contributions to Kingpin, please let me know.
|
||||
|
||||
**Current status.** Kingpin is largely feature stable. There hasn't been a need to add new features in a while, but there are some bugs that should be fixed.
|
||||
|
||||
**Why?** I no longer use Kingpin personally (I now use [kong](https://github.com/alecthomas/kong)). Rather than leave the project in a limbo of people filing issues and wondering why they're not being worked on, I believe this notice will more clearly set expectations.
|
||||
|
||||
# Kingpin - A Go (golang) command line and flag parser
|
||||
[](http://godoc.org/github.com/alecthomas/kingpin) [](https://github.com/alecthomas/kingpin/actions/workflows/ci.yml)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- MarkdownTOC -->
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Features](#features)
|
||||
- [User-visible changes between v1 and v2](#user-visible-changes-between-v1-and-v2)
|
||||
- [Flags can be used at any point after their definition.](#flags-can-be-used-at-any-point-after-their-definition)
|
||||
- [Short flags can be combined with their parameters](#short-flags-can-be-combined-with-their-parameters)
|
||||
- [API changes between v1 and v2](#api-changes-between-v1-and-v2)
|
||||
- [Versions](#versions)
|
||||
- [V2 is the current stable version](#v2-is-the-current-stable-version)
|
||||
- [V1 is the OLD stable version](#v1-is-the-old-stable-version)
|
||||
- [Change History](#change-history)
|
||||
- [Examples](#examples)
|
||||
- [Simple Example](#simple-example)
|
||||
- [Complex Example](#complex-example)
|
||||
- [Reference Documentation](#reference-documentation)
|
||||
- [Displaying errors and usage information](#displaying-errors-and-usage-information)
|
||||
- [Sub-commands](#sub-commands)
|
||||
- [Custom Parsers](#custom-parsers)
|
||||
- [Repeatable flags](#repeatable-flags)
|
||||
- [Boolean Values](#boolean-values)
|
||||
- [Default Values](#default-values)
|
||||
- [Place-holders in Help](#place-holders-in-help)
|
||||
- [Consuming all remaining arguments](#consuming-all-remaining-arguments)
|
||||
- [Bash/ZSH Shell Completion](#bashzsh-shell-completion)
|
||||
- [Supporting -h for help](#supporting--h-for-help)
|
||||
- [Custom help](#custom-help)
|
||||
|
||||
<!-- /MarkdownTOC -->
|
||||
|
||||
## Overview
|
||||
|
||||
Kingpin is a [fluent-style](http://en.wikipedia.org/wiki/Fluent_interface),
|
||||
type-safe command-line parser. It supports flags, nested commands, and
|
||||
positional arguments.
|
||||
|
||||
Install it with:
|
||||
|
||||
$ go get github.com/alecthomas/kingpin/v2
|
||||
|
||||
It looks like this:
|
||||
|
||||
```go
|
||||
var (
|
||||
verbose = kingpin.Flag("verbose", "Verbose mode.").Short('v').Bool()
|
||||
name = kingpin.Arg("name", "Name of user.").Required().String()
|
||||
)
|
||||
|
||||
func main() {
|
||||
kingpin.Parse()
|
||||
fmt.Printf("%v, %s\n", *verbose, *name)
|
||||
}
|
||||
```
|
||||
|
||||
More [examples](https://github.com/alecthomas/kingpin/tree/master/_examples) are available.
|
||||
|
||||
Second to parsing, providing the user with useful help is probably the most
|
||||
important thing a command-line parser does. Kingpin tries to provide detailed
|
||||
contextual help if `--help` is encountered at any point in the command line
|
||||
(excluding after `--`).
|
||||
|
||||
## Features
|
||||
|
||||
- Help output that isn't as ugly as sin.
|
||||
- Fully [customisable help](#custom-help), via Go templates.
|
||||
- Parsed, type-safe flags (`kingpin.Flag("f", "help").Int()`)
|
||||
- Parsed, type-safe positional arguments (`kingpin.Arg("a", "help").Int()`).
|
||||
- Parsed, type-safe, arbitrarily deep commands (`kingpin.Command("c", "help")`).
|
||||
- Support for required flags and required positional arguments (`kingpin.Flag("f", "").Required().Int()`).
|
||||
- Support for arbitrarily nested default commands (`command.Default()`).
|
||||
- Callbacks per command, flag and argument (`kingpin.Command("c", "").Action(myAction)`).
|
||||
- POSIX-style short flag combining (`-a -b` -> `-ab`).
|
||||
- Short-flag+parameter combining (`-a parm` -> `-aparm`).
|
||||
- Read command-line from files (`@<file>`).
|
||||
- Automatically generate man pages (`--help-man`).
|
||||
|
||||
## User-visible changes between v1 and v2
|
||||
|
||||
### Flags can be used at any point after their definition.
|
||||
|
||||
Flags can be specified at any point after their definition, not just
|
||||
*immediately after their associated command*. From the chat example below, the
|
||||
following used to be required:
|
||||
|
||||
```
|
||||
$ chat --server=chat.server.com:8080 post --image=~/Downloads/owls.jpg pics
|
||||
```
|
||||
|
||||
But the following will now work:
|
||||
|
||||
```
|
||||
$ chat post --server=chat.server.com:8080 --image=~/Downloads/owls.jpg pics
|
||||
```
|
||||
|
||||
### Short flags can be combined with their parameters
|
||||
|
||||
Previously, if a short flag was used, any argument to that flag would have to
|
||||
be separated by a space. That is no longer the case.
|
||||
|
||||
## API changes between v1 and v2
|
||||
|
||||
- `ParseWithFileExpansion()` is gone. The new parser directly supports expanding `@<file>`.
|
||||
- Added `FatalUsage()` and `FatalUsageContext()` for displaying an error + usage and terminating.
|
||||
- `Dispatch()` renamed to `Action()`.
|
||||
- Added `ParseContext()` for parsing a command line into its intermediate context form without executing.
|
||||
- Added `Terminate()` function to override the termination function.
|
||||
- Added `UsageForContextWithTemplate()` for printing usage via a custom template.
|
||||
- Added `UsageTemplate()` for overriding the default template to use. Two templates are included:
|
||||
1. `DefaultUsageTemplate` - default template.
|
||||
2. `CompactUsageTemplate` - compact command template for larger applications.
|
||||
|
||||
## Versions
|
||||
|
||||
The current stable version is [github.com/alecthomas/kingpin/v2](https://github.com/alecthomas/kingpin/v2). The previous version, [gopkg.in/alecthomas/kingpin.v1](https://gopkg.in/alecthomas/kingpin.v1), is deprecated and in maintenance mode.
|
||||
|
||||
### [V2](https://github.com/alecthomas/kingpin/v2) is the current stable version
|
||||
|
||||
Installation:
|
||||
|
||||
```sh
|
||||
$ go get github.com/alecthomas/kingpin/v2
|
||||
```
|
||||
|
||||
### [V1](https://gopkg.in/alecthomas/kingpin.v1) is the OLD stable version
|
||||
|
||||
Installation:
|
||||
|
||||
```sh
|
||||
$ go get gopkg.in/alecthomas/kingpin.v1
|
||||
```
|
||||
|
||||
## Change History
|
||||
|
||||
- *2015-09-19* -- Stable v2.1.0 release.
|
||||
- Added `command.Default()` to specify a default command to use if no other
|
||||
command matches. This allows for convenient user shortcuts.
|
||||
- Exposed `HelpFlag` and `VersionFlag` for further customisation.
|
||||
- `Action()` and `PreAction()` added and both now support an arbitrary
|
||||
number of callbacks.
|
||||
- `kingpin.SeparateOptionalFlagsUsageTemplate`.
|
||||
- `--help-long` and `--help-man` (hidden by default) flags.
|
||||
- Flags are "interspersed" by default, but can be disabled with `app.Interspersed(false)`.
|
||||
- Added flags for all simple builtin types (int8, uint16, etc.) and slice variants.
|
||||
- Use `app.Writer(os.Writer)` to specify the default writer for all output functions.
|
||||
- Dropped `os.Writer` prefix from all printf-like functions.
|
||||
|
||||
- *2015-05-22* -- Stable v2.0.0 release.
|
||||
- Initial stable release of v2.0.0.
|
||||
- Fully supports interspersed flags, commands and arguments.
|
||||
- Flags can be present at any point after their logical definition.
|
||||
- Application.Parse() terminates if commands are present and a command is not parsed.
|
||||
- Dispatch() -> Action().
|
||||
- Actions are dispatched after all values are populated.
|
||||
- Override termination function (defaults to os.Exit).
|
||||
- Override output stream (defaults to os.Stderr).
|
||||
- Templatised usage help, with default and compact templates.
|
||||
- Make error/usage functions more consistent.
|
||||
- Support argument expansion from files by default (with @<file>).
|
||||
- Fully public data model is available via .Model().
|
||||
- Parser has been completely refactored.
|
||||
- Parsing and execution has been split into distinct stages.
|
||||
- Use `go generate` to generate repeated flags.
|
||||
- Support combined short-flag+argument: -fARG.
|
||||
|
||||
- *2015-01-23* -- Stable v1.3.4 release.
|
||||
- Support "--" for separating flags from positional arguments.
|
||||
- Support loading flags from files (ParseWithFileExpansion()). Use @FILE as an argument.
|
||||
- Add post-app and post-cmd validation hooks. This allows arbitrary validation to be added.
|
||||
- A bunch of improvements to help usage and formatting.
|
||||
- Support arbitrarily nested sub-commands.
|
||||
|
||||
- *2014-07-08* -- Stable v1.2.0 release.
|
||||
- Pass any value through to `Strings()` when final argument.
|
||||
Allows for values that look like flags to be processed.
|
||||
- Allow `--help` to be used with commands.
|
||||
- Support `Hidden()` flags.
|
||||
- Parser for [units.Base2Bytes](https://github.com/alecthomas/units)
|
||||
type. Allows for flags like `--ram=512MB` or `--ram=1GB`.
|
||||
- Add an `Enum()` value, allowing only one of a set of values
|
||||
to be selected. eg. `Flag(...).Enum("debug", "info", "warning")`.
|
||||
|
||||
- *2014-06-27* -- Stable v1.1.0 release.
|
||||
- Bug fixes.
|
||||
- Always return an error (rather than panicing) when misconfigured.
|
||||
- `OpenFile(flag, perm)` value type added, for finer control over opening files.
|
||||
- Significantly improved usage formatting.
|
||||
|
||||
- *2014-06-19* -- Stable v1.0.0 release.
|
||||
- Support [cumulative positional](#consuming-all-remaining-arguments) arguments.
|
||||
- Return error rather than panic when there are fatal errors not caught by
|
||||
the type system. eg. when a default value is invalid.
|
||||
- Use gokpg.in.
|
||||
|
||||
- *2014-06-10* -- Place-holder streamlining.
|
||||
- Renamed `MetaVar` to `PlaceHolder`.
|
||||
- Removed `MetaVarFromDefault`. Kingpin now uses [heuristics](#place-holders-in-help)
|
||||
to determine what to display.
|
||||
|
||||
## Examples
|
||||
|
||||
### Simple Example
|
||||
|
||||
Kingpin can be used for simple flag+arg applications like so:
|
||||
|
||||
```
|
||||
$ ping --help
|
||||
usage: ping [<flags>] <ip> [<count>]
|
||||
|
||||
Flags:
|
||||
--debug Enable debug mode.
|
||||
--help Show help.
|
||||
-t, --timeout=5s Timeout waiting for ping.
|
||||
|
||||
Args:
|
||||
<ip> IP address to ping.
|
||||
[<count>] Number of packets to send
|
||||
$ ping 1.2.3.4 5
|
||||
Would ping: 1.2.3.4 with timeout 5s and count 5
|
||||
```
|
||||
|
||||
From the following source:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/alecthomas/kingpin/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
debug = kingpin.Flag("debug", "Enable debug mode.").Bool()
|
||||
timeout = kingpin.Flag("timeout", "Timeout waiting for ping.").Default("5s").Envar("PING_TIMEOUT").Short('t').Duration()
|
||||
ip = kingpin.Arg("ip", "IP address to ping.").Required().IP()
|
||||
count = kingpin.Arg("count", "Number of packets to send").Int()
|
||||
)
|
||||
|
||||
func main() {
|
||||
kingpin.Version("0.0.1")
|
||||
kingpin.Parse()
|
||||
fmt.Printf("Would ping: %s with timeout %s and count %d\n", *ip, *timeout, *count)
|
||||
}
|
||||
```
|
||||
|
||||
#### Reading arguments from a file
|
||||
Kingpin supports reading arguments from a file.
|
||||
Create a file with the corresponding arguments:
|
||||
```
|
||||
echo -t=5\n > args
|
||||
```
|
||||
And now supply it:
|
||||
```
|
||||
$ ping @args
|
||||
```
|
||||
|
||||
### Complex Example
|
||||
|
||||
Kingpin can also produce complex command-line applications with global flags,
|
||||
subcommands, and per-subcommand flags, like this:
|
||||
|
||||
```
|
||||
$ chat --help
|
||||
usage: chat [<flags>] <command> [<flags>] [<args> ...]
|
||||
|
||||
A command-line chat application.
|
||||
|
||||
Flags:
|
||||
--help Show help.
|
||||
--debug Enable debug mode.
|
||||
--server=127.0.0.1 Server address.
|
||||
|
||||
Commands:
|
||||
help [<command>]
|
||||
Show help for a command.
|
||||
|
||||
register <nick> <name>
|
||||
Register a new user.
|
||||
|
||||
post [<flags>] <channel> [<text>]
|
||||
Post a message to a channel.
|
||||
|
||||
$ chat help post
|
||||
usage: chat [<flags>] post [<flags>] <channel> [<text>]
|
||||
|
||||
Post a message to a channel.
|
||||
|
||||
Flags:
|
||||
--image=IMAGE Image to post.
|
||||
|
||||
Args:
|
||||
<channel> Channel to post to.
|
||||
[<text>] Text to post.
|
||||
|
||||
$ chat post --image=~/Downloads/owls.jpg pics
|
||||
...
|
||||
```
|
||||
|
||||
From this code:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"github.com/alecthomas/kingpin/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
app = kingpin.New("chat", "A command-line chat application.")
|
||||
debug = app.Flag("debug", "Enable debug mode.").Bool()
|
||||
serverIP = app.Flag("server", "Server address.").Default("127.0.0.1").IP()
|
||||
|
||||
register = app.Command("register", "Register a new user.")
|
||||
registerNick = register.Arg("nick", "Nickname for user.").Required().String()
|
||||
registerName = register.Arg("name", "Name of user.").Required().String()
|
||||
|
||||
post = app.Command("post", "Post a message to a channel.")
|
||||
postImage = post.Flag("image", "Image to post.").File()
|
||||
postChannel = post.Arg("channel", "Channel to post to.").Required().String()
|
||||
postText = post.Arg("text", "Text to post.").Strings()
|
||||
)
|
||||
|
||||
func main() {
|
||||
switch kingpin.MustParse(app.Parse(os.Args[1:])) {
|
||||
// Register user
|
||||
case register.FullCommand():
|
||||
println(*registerNick)
|
||||
|
||||
// Post message
|
||||
case post.FullCommand():
|
||||
if *postImage != nil {
|
||||
}
|
||||
text := strings.Join(*postText, " ")
|
||||
println("Post:", text)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Reference Documentation
|
||||
|
||||
### Displaying errors and usage information
|
||||
|
||||
Kingpin exports a set of functions to provide consistent errors and usage
|
||||
information to the user.
|
||||
|
||||
Error messages look something like this:
|
||||
|
||||
<app>: error: <message>
|
||||
|
||||
The functions on `Application` are:
|
||||
|
||||
Function | Purpose
|
||||
---------|--------------
|
||||
`Errorf(format, args)` | Display a printf formatted error to the user.
|
||||
`Fatalf(format, args)` | As with Errorf, but also call the termination handler.
|
||||
`FatalUsage(format, args)` | As with Fatalf, but also print contextual usage information.
|
||||
`FatalUsageContext(context, format, args)` | As with Fatalf, but also print contextual usage information from a `ParseContext`.
|
||||
`FatalIfError(err, format, args)` | Conditionally print an error prefixed with format+args, then call the termination handler
|
||||
|
||||
There are equivalent global functions in the kingpin namespace for the default
|
||||
`kingpin.CommandLine` instance.
|
||||
|
||||
### Sub-commands
|
||||
|
||||
Kingpin supports nested sub-commands, with separate flag and positional
|
||||
arguments per sub-command. Note that positional arguments may only occur after
|
||||
sub-commands.
|
||||
|
||||
For example:
|
||||
|
||||
```go
|
||||
var (
|
||||
deleteCommand = kingpin.Command("delete", "Delete an object.")
|
||||
deleteUserCommand = deleteCommand.Command("user", "Delete a user.")
|
||||
deleteUserUIDFlag = deleteUserCommand.Flag("uid", "Delete user by UID rather than username.")
|
||||
deleteUserUsername = deleteUserCommand.Arg("username", "Username to delete.")
|
||||
deletePostCommand = deleteCommand.Command("post", "Delete a post.")
|
||||
)
|
||||
|
||||
func main() {
|
||||
switch kingpin.Parse() {
|
||||
case deleteUserCommand.FullCommand():
|
||||
case deletePostCommand.FullCommand():
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Parsers
|
||||
|
||||
Kingpin supports both flag and positional argument parsers for converting to
|
||||
Go types. For example, some included parsers are `Int()`, `Float()`,
|
||||
`Duration()` and `ExistingFile()` (see [parsers.go](./parsers.go) for a complete list of included parsers).
|
||||
|
||||
Parsers conform to Go's [`flag.Value`](http://godoc.org/flag#Value)
|
||||
interface, so any existing implementations will work.
|
||||
|
||||
For example, a parser for accumulating HTTP header values might look like this:
|
||||
|
||||
```go
|
||||
type HTTPHeaderValue http.Header
|
||||
|
||||
func (h *HTTPHeaderValue) Set(value string) error {
|
||||
parts := strings.SplitN(value, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("expected HEADER:VALUE got '%s'", value)
|
||||
}
|
||||
(*http.Header)(h).Add(parts[0], parts[1])
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HTTPHeaderValue) String() string {
|
||||
return ""
|
||||
}
|
||||
```
|
||||
|
||||
As a convenience, I would recommend something like this:
|
||||
|
||||
```go
|
||||
func HTTPHeader(s Settings) (target *http.Header) {
|
||||
target = &http.Header{}
|
||||
s.SetValue((*HTTPHeaderValue)(target))
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
You would use it like so:
|
||||
|
||||
```go
|
||||
headers = HTTPHeader(kingpin.Flag("header", "Add a HTTP header to the request.").Short('H'))
|
||||
```
|
||||
|
||||
### Repeatable flags
|
||||
|
||||
Depending on the `Value` they hold, some flags may be repeated. The
|
||||
`IsCumulative() bool` function on `Value` tells if it's safe to call `Set()`
|
||||
multiple times or if an error should be raised if several values are passed.
|
||||
|
||||
The built-in `Value`s returning slices and maps, as well as `Counter` are
|
||||
examples of `Value`s that make a flag repeatable.
|
||||
|
||||
### Boolean values
|
||||
|
||||
Boolean values are uniquely managed by Kingpin. Each boolean flag will have a negative complement:
|
||||
`--<name>` and `--no-<name>`.
|
||||
|
||||
### Default Values
|
||||
|
||||
The default value is the zero value for a type. This can be overridden with
|
||||
the `Default(value...)` function on flags and arguments. This function accepts
|
||||
one or several strings, which are parsed by the value itself, so they *must*
|
||||
be compliant with the format expected.
|
||||
|
||||
### Place-holders in Help
|
||||
|
||||
The place-holder value for a flag is the value used in the help to describe
|
||||
the value of a non-boolean flag.
|
||||
|
||||
The value provided to PlaceHolder() is used if provided, then the value
|
||||
provided by Default() if provided, then finally the capitalised flag name is
|
||||
used.
|
||||
|
||||
Here are some examples of flags with various permutations:
|
||||
|
||||
--name=NAME // Flag(...).String()
|
||||
--name="Harry" // Flag(...).Default("Harry").String()
|
||||
--name=FULL-NAME // Flag(...).PlaceHolder("FULL-NAME").Default("Harry").String()
|
||||
|
||||
### Consuming all remaining arguments
|
||||
|
||||
A common command-line idiom is to use all remaining arguments for some
|
||||
purpose. eg. The following command accepts an arbitrary number of
|
||||
IP addresses as positional arguments:
|
||||
|
||||
./cmd ping 10.1.1.1 192.168.1.1
|
||||
|
||||
Such arguments are similar to [repeatable flags](#repeatable-flags), but for
|
||||
arguments. Therefore they use the same `IsCumulative() bool` function on the
|
||||
underlying `Value`, so the built-in `Value`s for which the `Set()` function
|
||||
can be called several times will consume multiple arguments.
|
||||
|
||||
To implement the above example with a custom `Value`, we might do something
|
||||
like this:
|
||||
|
||||
```go
|
||||
type ipList []net.IP
|
||||
|
||||
func (i *ipList) Set(value string) error {
|
||||
if ip := net.ParseIP(value); ip == nil {
|
||||
return fmt.Errorf("'%s' is not an IP address", value)
|
||||
} else {
|
||||
*i = append(*i, ip)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (i *ipList) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (i *ipList) IsCumulative() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func IPList(s Settings) (target *[]net.IP) {
|
||||
target = new([]net.IP)
|
||||
s.SetValue((*ipList)(target))
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
And use it like so:
|
||||
|
||||
```go
|
||||
ips := IPList(kingpin.Arg("ips", "IP addresses to ping."))
|
||||
```
|
||||
|
||||
### Bash/ZSH Shell Completion
|
||||
|
||||
By default, all flags and commands/subcommands generate completions
|
||||
internally.
|
||||
|
||||
Out of the box, CLI tools using kingpin should be able to take advantage
|
||||
of completion hinting for flags and commands. By specifying
|
||||
`--completion-bash` as the first argument, your CLI tool will show
|
||||
possible subcommands. By ending your argv with `--`, hints for flags
|
||||
will be shown.
|
||||
|
||||
To allow your end users to take advantage you must package a
|
||||
`/etc/bash_completion.d` script with your distribution (or the equivalent
|
||||
for your target platform/shell). An alternative is to instruct your end
|
||||
user to source a script from their `bash_profile` (or equivalent).
|
||||
|
||||
Fortunately Kingpin makes it easy to generate or source a script for use
|
||||
with end users shells. `./yourtool --completion-script-bash` and
|
||||
`./yourtool --completion-script-zsh` will generate these scripts for you.
|
||||
|
||||
**Installation by Package**
|
||||
|
||||
For the best user experience, you should bundle your pre-created
|
||||
completion script with your CLI tool and install it inside
|
||||
`/etc/bash_completion.d` (or equivalent). A good suggestion is to add
|
||||
this as an automated step to your build pipeline, in the implementation
|
||||
is improved for bug fixed.
|
||||
|
||||
**Installation by `bash_profile`**
|
||||
|
||||
Alternatively, instruct your users to add an additional statement to
|
||||
their `bash_profile` (or equivalent):
|
||||
|
||||
```
|
||||
eval "$(your-cli-tool --completion-script-bash)"
|
||||
```
|
||||
|
||||
Or for ZSH
|
||||
|
||||
```
|
||||
eval "$(your-cli-tool --completion-script-zsh)"
|
||||
```
|
||||
|
||||
#### Additional API
|
||||
To provide more flexibility, a completion option API has been
|
||||
exposed for flags to allow user defined completion options, to extend
|
||||
completions further than just EnumVar/Enum.
|
||||
|
||||
|
||||
**Provide Static Options**
|
||||
|
||||
When using an `Enum` or `EnumVar`, users are limited to only the options
|
||||
given. Maybe we wish to hint possible options to the user, but also
|
||||
allow them to provide their own custom option. `HintOptions` gives
|
||||
this functionality to flags.
|
||||
|
||||
```
|
||||
app := kingpin.New("completion", "My application with bash completion.")
|
||||
app.Flag("port", "Provide a port to connect to").
|
||||
Required().
|
||||
HintOptions("80", "443", "8080").
|
||||
IntVar(&c.port)
|
||||
```
|
||||
|
||||
**Provide Dynamic Options**
|
||||
Consider the case that you needed to read a local database or a file to
|
||||
provide suggestions. You can dynamically generate the options
|
||||
|
||||
```
|
||||
func listHosts() []string {
|
||||
// Provide a dynamic list of hosts from a hosts file or otherwise
|
||||
// for bash completion. In this example we simply return static slice.
|
||||
|
||||
// You could use this functionality to reach into a hosts file to provide
|
||||
// completion for a list of known hosts.
|
||||
return []string{"sshhost.example", "webhost.example", "ftphost.example"}
|
||||
}
|
||||
|
||||
app := kingpin.New("completion", "My application with bash completion.")
|
||||
app.Flag("flag-1", "").HintAction(listHosts).String()
|
||||
```
|
||||
|
||||
**EnumVar/Enum**
|
||||
When using `Enum` or `EnumVar`, any provided options will be automatically
|
||||
used for bash autocompletion. However, if you wish to provide a subset or
|
||||
different options, you can use `HintOptions` or `HintAction` which will override
|
||||
the default completion options for `Enum`/`EnumVar`.
|
||||
|
||||
|
||||
**Examples**
|
||||
You can see an in depth example of the completion API within
|
||||
`examples/completion/main.go`
|
||||
|
||||
|
||||
### Supporting -h for help
|
||||
|
||||
`kingpin.CommandLine.HelpFlag.Short('h')`
|
||||
|
||||
Short help is also available when creating a more complicated app:
|
||||
|
||||
```go
|
||||
var (
|
||||
app = kingpin.New("chat", "A command-line chat application.")
|
||||
// ...
|
||||
)
|
||||
|
||||
func main() {
|
||||
app.HelpFlag.Short('h')
|
||||
switch kingpin.MustParse(app.Parse(os.Args[1:])) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Custom help
|
||||
|
||||
Kingpin v2 supports templatised help using the text/template library (actually, [a fork](https://github.com/alecthomas/template)).
|
||||
|
||||
You can specify the template to use with the [Application.UsageTemplate()](http://godoc.org/github.com/alecthomas/kingpin/v2#Application.UsageTemplate) function.
|
||||
|
||||
There are four included templates: `kingpin.DefaultUsageTemplate` is the default,
|
||||
`kingpin.CompactUsageTemplate` provides a more compact representation for more complex command-line structures,
|
||||
`kingpin.SeparateOptionalFlagsUsageTemplate` looks like the default template, but splits required
|
||||
and optional command flags into separate lists, and `kingpin.ManPageTemplate` is used to generate man pages.
|
||||
|
||||
See the above templates for examples of usage, and the the function [UsageForContextWithTemplate()](https://github.com/alecthomas/kingpin/blob/master/usage.go#L198) method for details on the context.
|
||||
|
||||
#### Default help template
|
||||
|
||||
```
|
||||
$ go run ./examples/curl/curl.go --help
|
||||
usage: curl [<flags>] <command> [<args> ...]
|
||||
|
||||
An example implementation of curl.
|
||||
|
||||
Flags:
|
||||
--help Show help.
|
||||
-t, --timeout=5s Set connection timeout.
|
||||
-H, --headers=HEADER=VALUE
|
||||
Add HTTP headers to the request.
|
||||
|
||||
Commands:
|
||||
help [<command>...]
|
||||
Show help.
|
||||
|
||||
get url <url>
|
||||
Retrieve a URL.
|
||||
|
||||
get file <file>
|
||||
Retrieve a file.
|
||||
|
||||
post [<flags>] <url>
|
||||
POST a resource.
|
||||
```
|
||||
|
||||
#### Compact help template
|
||||
|
||||
```
|
||||
$ go run ./examples/curl/curl.go --help
|
||||
usage: curl [<flags>] <command> [<args> ...]
|
||||
|
||||
An example implementation of curl.
|
||||
|
||||
Flags:
|
||||
--help Show help.
|
||||
-t, --timeout=5s Set connection timeout.
|
||||
-H, --headers=HEADER=VALUE
|
||||
Add HTTP headers to the request.
|
||||
|
||||
Commands:
|
||||
help [<command>...]
|
||||
get [<flags>]
|
||||
url <url>
|
||||
file <file>
|
||||
post [<flags>] <url>
|
||||
```
|
|
@ -0,0 +1,42 @@
|
|||
package kingpin
|
||||
|
||||
// Action callback executed at various stages after all values are populated.
|
||||
// The application, commands, arguments and flags all have corresponding
|
||||
// actions.
|
||||
type Action func(*ParseContext) error
|
||||
|
||||
type actionMixin struct {
|
||||
actions []Action
|
||||
preActions []Action
|
||||
}
|
||||
|
||||
type actionApplier interface {
|
||||
applyActions(*ParseContext) error
|
||||
applyPreActions(*ParseContext) error
|
||||
}
|
||||
|
||||
func (a *actionMixin) addAction(action Action) {
|
||||
a.actions = append(a.actions, action)
|
||||
}
|
||||
|
||||
func (a *actionMixin) addPreAction(action Action) {
|
||||
a.preActions = append(a.preActions, action)
|
||||
}
|
||||
|
||||
func (a *actionMixin) applyActions(context *ParseContext) error {
|
||||
for _, action := range a.actions {
|
||||
if err := action(context); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *actionMixin) applyPreActions(context *ParseContext) error {
|
||||
for _, preAction := range a.preActions {
|
||||
if err := preAction(context); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,703 @@
|
|||
package kingpin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCommandNotSpecified = fmt.Errorf("command not specified")
|
||||
)
|
||||
|
||||
var (
|
||||
envarTransformRegexp = regexp.MustCompile(`[^a-zA-Z0-9_]+`)
|
||||
)
|
||||
|
||||
type ApplicationValidator func(*Application) error
|
||||
|
||||
// An Application contains the definitions of flags, arguments and commands
|
||||
// for an application.
|
||||
type Application struct {
|
||||
cmdMixin
|
||||
initialized bool
|
||||
|
||||
Name string
|
||||
Help string
|
||||
|
||||
author string
|
||||
version string
|
||||
errorWriter io.Writer // Destination for errors.
|
||||
usageWriter io.Writer // Destination for usage
|
||||
usageTemplate string
|
||||
usageFuncs template.FuncMap
|
||||
validator ApplicationValidator
|
||||
terminate func(status int) // See Terminate()
|
||||
noInterspersed bool // can flags be interspersed with args (or must they come first)
|
||||
defaultEnvars bool
|
||||
completion bool
|
||||
|
||||
// Help flag. Exposed for user customisation.
|
||||
HelpFlag *FlagClause
|
||||
// Help command. Exposed for user customisation. May be nil.
|
||||
HelpCommand *CmdClause
|
||||
// Version flag. Exposed for user customisation. May be nil.
|
||||
VersionFlag *FlagClause
|
||||
}
|
||||
|
||||
// New creates a new Kingpin application instance.
|
||||
func New(name, help string) *Application {
|
||||
a := &Application{
|
||||
Name: name,
|
||||
Help: help,
|
||||
errorWriter: os.Stderr, // Left for backwards compatibility purposes.
|
||||
usageWriter: os.Stderr,
|
||||
usageTemplate: DefaultUsageTemplate,
|
||||
terminate: os.Exit,
|
||||
}
|
||||
a.flagGroup = newFlagGroup()
|
||||
a.argGroup = newArgGroup()
|
||||
a.cmdGroup = newCmdGroup(a)
|
||||
a.HelpFlag = a.Flag("help", "Show context-sensitive help (also try --help-long and --help-man).")
|
||||
a.HelpFlag.Bool()
|
||||
a.Flag("help-long", "Generate long help.").Hidden().PreAction(a.generateLongHelp).Bool()
|
||||
a.Flag("help-man", "Generate a man page.").Hidden().PreAction(a.generateManPage).Bool()
|
||||
a.Flag("completion-bash", "Output possible completions for the given args.").Hidden().BoolVar(&a.completion)
|
||||
a.Flag("completion-script-bash", "Generate completion script for bash.").Hidden().PreAction(a.generateBashCompletionScript).Bool()
|
||||
a.Flag("completion-script-zsh", "Generate completion script for ZSH.").Hidden().PreAction(a.generateZSHCompletionScript).Bool()
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *Application) generateLongHelp(c *ParseContext) error {
|
||||
a.Writer(os.Stdout)
|
||||
if err := a.UsageForContextWithTemplate(c, 2, LongHelpTemplate); err != nil {
|
||||
return err
|
||||
}
|
||||
a.terminate(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) generateManPage(c *ParseContext) error {
|
||||
a.Writer(os.Stdout)
|
||||
if err := a.UsageForContextWithTemplate(c, 2, ManPageTemplate); err != nil {
|
||||
return err
|
||||
}
|
||||
a.terminate(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) generateBashCompletionScript(c *ParseContext) error {
|
||||
a.Writer(os.Stdout)
|
||||
if err := a.UsageForContextWithTemplate(c, 2, BashCompletionTemplate); err != nil {
|
||||
return err
|
||||
}
|
||||
a.terminate(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) generateZSHCompletionScript(c *ParseContext) error {
|
||||
a.Writer(os.Stdout)
|
||||
if err := a.UsageForContextWithTemplate(c, 2, ZshCompletionTemplate); err != nil {
|
||||
return err
|
||||
}
|
||||
a.terminate(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultEnvars configures all flags (that do not already have an associated
|
||||
// envar) to use a default environment variable in the form "<app>_<flag>".
|
||||
//
|
||||
// For example, if the application is named "foo" and a flag is named "bar-
|
||||
// waz" the environment variable: "FOO_BAR_WAZ".
|
||||
func (a *Application) DefaultEnvars() *Application {
|
||||
a.defaultEnvars = true
|
||||
return a
|
||||
}
|
||||
|
||||
// Terminate specifies the termination handler. Defaults to os.Exit(status).
|
||||
// If nil is passed, a no-op function will be used.
|
||||
func (a *Application) Terminate(terminate func(int)) *Application {
|
||||
if terminate == nil {
|
||||
terminate = func(int) {}
|
||||
}
|
||||
a.terminate = terminate
|
||||
return a
|
||||
}
|
||||
|
||||
// Writer specifies the writer to use for usage and errors. Defaults to os.Stderr.
|
||||
// DEPRECATED: See ErrorWriter and UsageWriter.
|
||||
func (a *Application) Writer(w io.Writer) *Application {
|
||||
a.errorWriter = w
|
||||
a.usageWriter = w
|
||||
return a
|
||||
}
|
||||
|
||||
// ErrorWriter sets the io.Writer to use for errors.
|
||||
func (a *Application) ErrorWriter(w io.Writer) *Application {
|
||||
a.errorWriter = w
|
||||
return a
|
||||
}
|
||||
|
||||
// UsageWriter sets the io.Writer to use for errors.
|
||||
func (a *Application) UsageWriter(w io.Writer) *Application {
|
||||
a.usageWriter = w
|
||||
return a
|
||||
}
|
||||
|
||||
// UsageTemplate specifies the text template to use when displaying usage
|
||||
// information. The default is UsageTemplate.
|
||||
func (a *Application) UsageTemplate(template string) *Application {
|
||||
a.usageTemplate = template
|
||||
return a
|
||||
}
|
||||
|
||||
// UsageFuncs adds extra functions that can be used in the usage template.
|
||||
func (a *Application) UsageFuncs(funcs template.FuncMap) *Application {
|
||||
a.usageFuncs = funcs
|
||||
return a
|
||||
}
|
||||
|
||||
// Validate sets a validation function to run when parsing.
|
||||
func (a *Application) Validate(validator ApplicationValidator) *Application {
|
||||
a.validator = validator
|
||||
return a
|
||||
}
|
||||
|
||||
// ParseContext parses the given command line and returns the fully populated
|
||||
// ParseContext.
|
||||
func (a *Application) ParseContext(args []string) (*ParseContext, error) {
|
||||
return a.parseContext(false, args)
|
||||
}
|
||||
|
||||
func (a *Application) parseContext(ignoreDefault bool, args []string) (*ParseContext, error) {
|
||||
if err := a.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
context := tokenize(args, ignoreDefault)
|
||||
err := parse(context, a)
|
||||
return context, err
|
||||
}
|
||||
|
||||
// Parse parses command-line arguments. It returns the selected command and an
|
||||
// error. The selected command will be a space separated subcommand, if
|
||||
// subcommands have been configured.
|
||||
//
|
||||
// This will populate all flag and argument values, call all callbacks, and so
|
||||
// on.
|
||||
func (a *Application) Parse(args []string) (command string, err error) {
|
||||
|
||||
context, parseErr := a.ParseContext(args)
|
||||
selected := []string{}
|
||||
var setValuesErr error
|
||||
|
||||
if context == nil {
|
||||
// Since we do not throw error immediately, there could be a case
|
||||
// where a context returns nil. Protect against that.
|
||||
return "", parseErr
|
||||
}
|
||||
|
||||
if err = a.setDefaults(context); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
selected, setValuesErr = a.setValues(context)
|
||||
|
||||
if err = a.applyPreActions(context, !a.completion); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if a.completion {
|
||||
a.generateBashCompletion(context)
|
||||
a.terminate(0)
|
||||
} else {
|
||||
if parseErr != nil {
|
||||
return "", parseErr
|
||||
}
|
||||
|
||||
a.maybeHelp(context)
|
||||
if !context.EOL() {
|
||||
return "", fmt.Errorf("unexpected argument '%s'", context.Peek())
|
||||
}
|
||||
|
||||
if setValuesErr != nil {
|
||||
return "", setValuesErr
|
||||
}
|
||||
|
||||
command, err = a.execute(context, selected)
|
||||
if err == ErrCommandNotSpecified {
|
||||
a.writeUsage(context, nil)
|
||||
}
|
||||
}
|
||||
return command, err
|
||||
}
|
||||
|
||||
func (a *Application) writeUsage(context *ParseContext, err error) {
|
||||
if err != nil {
|
||||
a.Errorf("%s", err)
|
||||
}
|
||||
if err := a.UsageForContext(context); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err != nil {
|
||||
a.terminate(1)
|
||||
} else {
|
||||
a.terminate(0)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Application) maybeHelp(context *ParseContext) {
|
||||
for _, element := range context.Elements {
|
||||
if flag, ok := element.Clause.(*FlagClause); ok && flag == a.HelpFlag {
|
||||
// Re-parse the command-line ignoring defaults, so that help works correctly.
|
||||
context, _ = a.parseContext(true, context.rawArgs)
|
||||
a.writeUsage(context, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Version adds a --version flag for displaying the application version.
|
||||
func (a *Application) Version(version string) *Application {
|
||||
a.version = version
|
||||
a.VersionFlag = a.Flag("version", "Show application version.").PreAction(func(*ParseContext) error {
|
||||
fmt.Fprintln(a.usageWriter, version)
|
||||
a.terminate(0)
|
||||
return nil
|
||||
})
|
||||
a.VersionFlag.Bool()
|
||||
return a
|
||||
}
|
||||
|
||||
// Author sets the author output by some help templates.
|
||||
func (a *Application) Author(author string) *Application {
|
||||
a.author = author
|
||||
return a
|
||||
}
|
||||
|
||||
// Action callback to call when all values are populated and parsing is
|
||||
// complete, but before any command, flag or argument actions.
|
||||
//
|
||||
// All Action() callbacks are called in the order they are encountered on the
|
||||
// command line.
|
||||
func (a *Application) Action(action Action) *Application {
|
||||
a.addAction(action)
|
||||
return a
|
||||
}
|
||||
|
||||
// Action called after parsing completes but before validation and execution.
|
||||
func (a *Application) PreAction(action Action) *Application {
|
||||
a.addPreAction(action)
|
||||
return a
|
||||
}
|
||||
|
||||
// Command adds a new top-level command.
|
||||
func (a *Application) Command(name, help string) *CmdClause {
|
||||
return a.addCommand(name, help)
|
||||
}
|
||||
|
||||
// Interspersed control if flags can be interspersed with positional arguments
|
||||
//
|
||||
// true (the default) means that they can, false means that all the flags must appear before the first positional arguments.
|
||||
func (a *Application) Interspersed(interspersed bool) *Application {
|
||||
a.noInterspersed = !interspersed
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *Application) defaultEnvarPrefix() string {
|
||||
if a.defaultEnvars {
|
||||
return a.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (a *Application) init() error {
|
||||
if a.initialized {
|
||||
return nil
|
||||
}
|
||||
if a.cmdGroup.have() && a.argGroup.have() {
|
||||
return fmt.Errorf("can't mix top-level Arg()s with Command()s")
|
||||
}
|
||||
|
||||
// If we have subcommands, add a help command at the top-level.
|
||||
if a.cmdGroup.have() {
|
||||
var command []string
|
||||
a.HelpCommand = a.Command("help", "Show help.").PreAction(func(context *ParseContext) error {
|
||||
a.Usage(command)
|
||||
a.terminate(0)
|
||||
return nil
|
||||
})
|
||||
a.HelpCommand.Arg("command", "Show help on command.").StringsVar(&command)
|
||||
// Make help first command.
|
||||
l := len(a.commandOrder)
|
||||
a.commandOrder = append(a.commandOrder[l-1:l], a.commandOrder[:l-1]...)
|
||||
}
|
||||
|
||||
if err := a.flagGroup.init(a.defaultEnvarPrefix()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.cmdGroup.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.argGroup.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, cmd := range a.commands {
|
||||
if err := cmd.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
flagGroups := []*flagGroup{a.flagGroup}
|
||||
for _, cmd := range a.commandOrder {
|
||||
if err := checkDuplicateFlags(cmd, flagGroups); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
a.initialized = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Recursively check commands for duplicate flags.
|
||||
func checkDuplicateFlags(current *CmdClause, flagGroups []*flagGroup) error {
|
||||
// Check for duplicates.
|
||||
for _, flags := range flagGroups {
|
||||
for _, flag := range current.flagOrder {
|
||||
if flag.shorthand != 0 {
|
||||
if _, ok := flags.short[string(flag.shorthand)]; ok {
|
||||
return fmt.Errorf("duplicate short flag -%c", flag.shorthand)
|
||||
}
|
||||
}
|
||||
if _, ok := flags.long[flag.name]; ok {
|
||||
return fmt.Errorf("duplicate long flag --%s", flag.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
flagGroups = append(flagGroups, current.flagGroup)
|
||||
// Check subcommands.
|
||||
for _, subcmd := range current.commandOrder {
|
||||
if err := checkDuplicateFlags(subcmd, flagGroups); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) execute(context *ParseContext, selected []string) (string, error) {
|
||||
var err error
|
||||
|
||||
if err = a.validateRequired(context); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = a.applyValidators(context); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = a.applyActions(context); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
command := strings.Join(selected, " ")
|
||||
if command == "" && a.cmdGroup.have() {
|
||||
return "", ErrCommandNotSpecified
|
||||
}
|
||||
return command, err
|
||||
}
|
||||
|
||||
func (a *Application) setDefaults(context *ParseContext) error {
|
||||
flagElements := map[string]*ParseElement{}
|
||||
for _, element := range context.Elements {
|
||||
if flag, ok := element.Clause.(*FlagClause); ok {
|
||||
if flag.name == "help" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if flag.name == "version" {
|
||||
return nil
|
||||
}
|
||||
flagElements[flag.name] = element
|
||||
}
|
||||
}
|
||||
|
||||
argElements := map[string]*ParseElement{}
|
||||
for _, element := range context.Elements {
|
||||
if arg, ok := element.Clause.(*ArgClause); ok {
|
||||
argElements[arg.name] = element
|
||||
}
|
||||
}
|
||||
|
||||
// Check required flags and set defaults.
|
||||
for _, flag := range context.flags.long {
|
||||
if flagElements[flag.name] == nil {
|
||||
if err := flag.setDefault(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, arg := range context.arguments.args {
|
||||
if argElements[arg.name] == nil {
|
||||
if err := arg.setDefault(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) validateRequired(context *ParseContext) error {
|
||||
flagElements := map[string]*ParseElement{}
|
||||
for _, element := range context.Elements {
|
||||
if flag, ok := element.Clause.(*FlagClause); ok {
|
||||
flagElements[flag.name] = element
|
||||
}
|
||||
}
|
||||
|
||||
argElements := map[string]*ParseElement{}
|
||||
for _, element := range context.Elements {
|
||||
if arg, ok := element.Clause.(*ArgClause); ok {
|
||||
argElements[arg.name] = element
|
||||
}
|
||||
}
|
||||
|
||||
// Check required flags and set defaults.
|
||||
var missingFlags []string
|
||||
for _, flag := range context.flags.long {
|
||||
if flagElements[flag.name] == nil {
|
||||
// Check required flags were provided.
|
||||
if flag.needsValue() {
|
||||
missingFlags = append(missingFlags, fmt.Sprintf("'--%s'", flag.name))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(missingFlags) != 0 {
|
||||
return fmt.Errorf("required flag(s) %s not provided", strings.Join(missingFlags, ", "))
|
||||
}
|
||||
|
||||
for _, arg := range context.arguments.args {
|
||||
if argElements[arg.name] == nil {
|
||||
if arg.needsValue() {
|
||||
return fmt.Errorf("required argument '%s' not provided", arg.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) setValues(context *ParseContext) (selected []string, err error) {
|
||||
// Set all arg and flag values.
|
||||
var (
|
||||
lastCmd *CmdClause
|
||||
flagSet = map[string]struct{}{}
|
||||
)
|
||||
for _, element := range context.Elements {
|
||||
switch clause := element.Clause.(type) {
|
||||
case *FlagClause:
|
||||
if _, ok := flagSet[clause.name]; ok {
|
||||
if v, ok := clause.value.(repeatableFlag); !ok || !v.IsCumulative() {
|
||||
return nil, fmt.Errorf("flag '%s' cannot be repeated", clause.name)
|
||||
}
|
||||
}
|
||||
if err = clause.value.Set(*element.Value); err != nil {
|
||||
return
|
||||
}
|
||||
flagSet[clause.name] = struct{}{}
|
||||
|
||||
case *ArgClause:
|
||||
if err = clause.value.Set(*element.Value); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case *CmdClause:
|
||||
selected = append(selected, clause.name)
|
||||
lastCmd = clause
|
||||
}
|
||||
}
|
||||
|
||||
if lastCmd != nil && len(lastCmd.commands) > 0 {
|
||||
return nil, fmt.Errorf("must select a subcommand of '%s'", lastCmd.FullCommand())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (a *Application) applyValidators(context *ParseContext) (err error) {
|
||||
// Call command validation functions.
|
||||
for _, element := range context.Elements {
|
||||
if cmd, ok := element.Clause.(*CmdClause); ok && cmd.validator != nil {
|
||||
if err = cmd.validator(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if a.validator != nil {
|
||||
err = a.validator(a)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *Application) applyPreActions(context *ParseContext, dispatch bool) error {
|
||||
if err := a.actionMixin.applyPreActions(context); err != nil {
|
||||
return err
|
||||
}
|
||||
// Dispatch to actions.
|
||||
if dispatch {
|
||||
for _, element := range context.Elements {
|
||||
if applier, ok := element.Clause.(actionApplier); ok {
|
||||
if err := applier.applyPreActions(context); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) applyActions(context *ParseContext) error {
|
||||
if err := a.actionMixin.applyActions(context); err != nil {
|
||||
return err
|
||||
}
|
||||
// Dispatch to actions.
|
||||
for _, element := range context.Elements {
|
||||
if applier, ok := element.Clause.(actionApplier); ok {
|
||||
if err := applier.applyActions(context); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Errorf prints an error message to w in the format "<appname>: error: <message>".
|
||||
func (a *Application) Errorf(format string, args ...interface{}) {
|
||||
fmt.Fprintf(a.errorWriter, a.Name+": error: "+format+"\n", args...)
|
||||
}
|
||||
|
||||
// Fatalf writes a formatted error to w then terminates with exit status 1.
|
||||
func (a *Application) Fatalf(format string, args ...interface{}) {
|
||||
a.Errorf(format, args...)
|
||||
a.terminate(1)
|
||||
}
|
||||
|
||||
// FatalUsage prints an error message followed by usage information, then
|
||||
// exits with a non-zero status.
|
||||
func (a *Application) FatalUsage(format string, args ...interface{}) {
|
||||
a.Errorf(format, args...)
|
||||
// Force usage to go to error output.
|
||||
a.usageWriter = a.errorWriter
|
||||
a.Usage([]string{})
|
||||
a.terminate(1)
|
||||
}
|
||||
|
||||
// FatalUsageContext writes a printf formatted error message to w, then usage
|
||||
// information for the given ParseContext, before exiting.
|
||||
func (a *Application) FatalUsageContext(context *ParseContext, format string, args ...interface{}) {
|
||||
a.Errorf(format, args...)
|
||||
if err := a.UsageForContext(context); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
a.terminate(1)
|
||||
}
|
||||
|
||||
// FatalIfError prints an error and exits if err is not nil. The error is printed
|
||||
// with the given formatted string, if any.
|
||||
func (a *Application) FatalIfError(err error, format string, args ...interface{}) {
|
||||
if err != nil {
|
||||
prefix := ""
|
||||
if format != "" {
|
||||
prefix = fmt.Sprintf(format, args...) + ": "
|
||||
}
|
||||
a.Errorf(prefix+"%s", err)
|
||||
a.terminate(1)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Application) completionOptions(context *ParseContext) []string {
|
||||
args := context.rawArgs
|
||||
|
||||
var (
|
||||
currArg string
|
||||
prevArg string
|
||||
target cmdMixin
|
||||
)
|
||||
|
||||
numArgs := len(args)
|
||||
if numArgs > 1 {
|
||||
args = args[1:]
|
||||
currArg = args[len(args)-1]
|
||||
}
|
||||
if numArgs > 2 {
|
||||
prevArg = args[len(args)-2]
|
||||
}
|
||||
|
||||
target = a.cmdMixin
|
||||
if context.SelectedCommand != nil {
|
||||
// A subcommand was in use. We will use it as the target
|
||||
target = context.SelectedCommand.cmdMixin
|
||||
}
|
||||
|
||||
if (currArg != "" && strings.HasPrefix(currArg, "--")) || strings.HasPrefix(prevArg, "--") {
|
||||
if context.argsOnly {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Perform completion for A flag. The last/current argument started with "-"
|
||||
var (
|
||||
flagName string // The name of a flag if given (could be half complete)
|
||||
flagValue string // The value assigned to a flag (if given) (could be half complete)
|
||||
)
|
||||
|
||||
if strings.HasPrefix(prevArg, "--") && !strings.HasPrefix(currArg, "--") {
|
||||
// Matches: ./myApp --flag value
|
||||
// Wont Match: ./myApp --flag --
|
||||
flagName = prevArg[2:] // Strip the "--"
|
||||
flagValue = currArg
|
||||
} else if strings.HasPrefix(currArg, "--") {
|
||||
// Matches: ./myApp --flag --
|
||||
// Matches: ./myApp --flag somevalue --
|
||||
// Matches: ./myApp --
|
||||
flagName = currArg[2:] // Strip the "--"
|
||||
}
|
||||
|
||||
options, flagMatched, valueMatched := target.FlagCompletion(flagName, flagValue)
|
||||
if valueMatched {
|
||||
// Value Matched. Show cmdCompletions
|
||||
return target.CmdCompletion(context)
|
||||
}
|
||||
|
||||
// Add top level flags if we're not at the top level and no match was found.
|
||||
if context.SelectedCommand != nil && !flagMatched {
|
||||
topOptions, topFlagMatched, topValueMatched := a.FlagCompletion(flagName, flagValue)
|
||||
if topValueMatched {
|
||||
// Value Matched. Back to cmdCompletions
|
||||
return target.CmdCompletion(context)
|
||||
}
|
||||
|
||||
if topFlagMatched {
|
||||
// Top level had a flag which matched the input. Return it's options.
|
||||
options = topOptions
|
||||
} else {
|
||||
// Add top level flags
|
||||
options = append(options, topOptions...)
|
||||
}
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
// Perform completion for sub commands and arguments.
|
||||
return target.CmdCompletion(context)
|
||||
}
|
||||
|
||||
func (a *Application) generateBashCompletion(context *ParseContext) {
|
||||
options := a.completionOptions(context)
|
||||
fmt.Printf("%s", strings.Join(options, "\n"))
|
||||
}
|
||||
|
||||
func envarTransform(name string) string {
|
||||
return strings.ToUpper(envarTransformRegexp.ReplaceAllString(name, "_"))
|
||||
}
|
|
@ -0,0 +1,205 @@
|
|||
package kingpin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type argGroup struct {
|
||||
args []*ArgClause
|
||||
}
|
||||
|
||||
func newArgGroup() *argGroup {
|
||||
return &argGroup{}
|
||||
}
|
||||
|
||||
func (a *argGroup) have() bool {
|
||||
return len(a.args) > 0
|
||||
}
|
||||
|
||||
// GetArg gets an argument definition.
|
||||
//
|
||||
// This allows existing arguments to be modified after definition but before parsing. Useful for
|
||||
// modular applications.
|
||||
func (a *argGroup) GetArg(name string) *ArgClause {
|
||||
for _, arg := range a.args {
|
||||
if arg.name == name {
|
||||
return arg
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *argGroup) Arg(name, help string) *ArgClause {
|
||||
arg := newArg(name, help)
|
||||
a.args = append(a.args, arg)
|
||||
return arg
|
||||
}
|
||||
|
||||
func (a *argGroup) init() error {
|
||||
required := 0
|
||||
seen := map[string]struct{}{}
|
||||
previousArgMustBeLast := false
|
||||
for i, arg := range a.args {
|
||||
if previousArgMustBeLast {
|
||||
return fmt.Errorf("Args() can't be followed by another argument '%s'", arg.name)
|
||||
}
|
||||
if arg.consumesRemainder() {
|
||||
previousArgMustBeLast = true
|
||||
}
|
||||
if _, ok := seen[arg.name]; ok {
|
||||
return fmt.Errorf("duplicate argument '%s'", arg.name)
|
||||
}
|
||||
seen[arg.name] = struct{}{}
|
||||
if arg.required && required != i {
|
||||
return fmt.Errorf("required arguments found after non-required")
|
||||
}
|
||||
if arg.required {
|
||||
required++
|
||||
}
|
||||
if err := arg.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ArgClause struct {
|
||||
actionMixin
|
||||
parserMixin
|
||||
completionsMixin
|
||||
envarMixin
|
||||
name string
|
||||
help string
|
||||
defaultValues []string
|
||||
placeholder string
|
||||
hidden bool
|
||||
required bool
|
||||
}
|
||||
|
||||
func newArg(name, help string) *ArgClause {
|
||||
a := &ArgClause{
|
||||
name: name,
|
||||
help: help,
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ArgClause) setDefault() error {
|
||||
if a.HasEnvarValue() {
|
||||
if v, ok := a.value.(remainderArg); !ok || !v.IsCumulative() {
|
||||
// Use the value as-is
|
||||
return a.value.Set(a.GetEnvarValue())
|
||||
}
|
||||
for _, value := range a.GetSplitEnvarValue() {
|
||||
if err := a.value.Set(value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(a.defaultValues) > 0 {
|
||||
for _, defaultValue := range a.defaultValues {
|
||||
if err := a.value.Set(defaultValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ArgClause) needsValue() bool {
|
||||
haveDefault := len(a.defaultValues) > 0
|
||||
return a.required && !(haveDefault || a.HasEnvarValue())
|
||||
}
|
||||
|
||||
func (a *ArgClause) consumesRemainder() bool {
|
||||
if r, ok := a.value.(remainderArg); ok {
|
||||
return r.IsCumulative()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Hidden hides the argument from usage but still allows it to be used.
|
||||
func (a *ArgClause) Hidden() *ArgClause {
|
||||
a.hidden = true
|
||||
return a
|
||||
}
|
||||
|
||||
// PlaceHolder sets the place-holder string used for arg values in the help. The
|
||||
// default behaviour is to use the arg name between < > brackets.
|
||||
func (a *ArgClause) PlaceHolder(value string) *ArgClause {
|
||||
a.placeholder = value
|
||||
return a
|
||||
}
|
||||
|
||||
// Required arguments must be input by the user. They can not have a Default() value provided.
|
||||
func (a *ArgClause) Required() *ArgClause {
|
||||
a.required = true
|
||||
return a
|
||||
}
|
||||
|
||||
// Default values for this argument. They *must* be parseable by the value of the argument.
|
||||
func (a *ArgClause) Default(values ...string) *ArgClause {
|
||||
a.defaultValues = values
|
||||
return a
|
||||
}
|
||||
|
||||
// Envar overrides the default value(s) for a flag from an environment variable,
|
||||
// if it is set. Several default values can be provided by using new lines to
|
||||
// separate them.
|
||||
func (a *ArgClause) Envar(name string) *ArgClause {
|
||||
a.envar = name
|
||||
a.noEnvar = false
|
||||
return a
|
||||
}
|
||||
|
||||
// NoEnvar forces environment variable defaults to be disabled for this flag.
|
||||
// Most useful in conjunction with app.DefaultEnvars().
|
||||
func (a *ArgClause) NoEnvar() *ArgClause {
|
||||
a.envar = ""
|
||||
a.noEnvar = true
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ArgClause) Action(action Action) *ArgClause {
|
||||
a.addAction(action)
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ArgClause) PreAction(action Action) *ArgClause {
|
||||
a.addPreAction(action)
|
||||
return a
|
||||
}
|
||||
|
||||
// HintAction registers a HintAction (function) for the arg to provide completions
|
||||
func (a *ArgClause) HintAction(action HintAction) *ArgClause {
|
||||
a.addHintAction(action)
|
||||
return a
|
||||
}
|
||||
|
||||
// HintOptions registers any number of options for the flag to provide completions
|
||||
func (a *ArgClause) HintOptions(options ...string) *ArgClause {
|
||||
a.addHintAction(func() []string {
|
||||
return options
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
// Help sets the help message.
|
||||
func (a *ArgClause) Help(help string) *ArgClause {
|
||||
a.help = help
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ArgClause) init() error {
|
||||
if a.required && len(a.defaultValues) > 0 {
|
||||
return fmt.Errorf("required argument '%s' with unusable default value", a.name)
|
||||
}
|
||||
if a.value == nil {
|
||||
return fmt.Errorf("no parser defined for arg '%s'", a.name)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,325 @@
|
|||
package kingpin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type cmdMixin struct {
|
||||
*flagGroup
|
||||
*argGroup
|
||||
*cmdGroup
|
||||
actionMixin
|
||||
}
|
||||
|
||||
// CmdCompletion returns completion options for arguments, if that's where
|
||||
// parsing left off, or commands if there aren't any unsatisfied args.
|
||||
func (c *cmdMixin) CmdCompletion(context *ParseContext) []string {
|
||||
var options []string
|
||||
|
||||
// Count args already satisfied - we won't complete those, and add any
|
||||
// default commands' alternatives, since they weren't listed explicitly
|
||||
// and the user may want to explicitly list something else.
|
||||
argsSatisfied := 0
|
||||
allSatisfied := false
|
||||
ElementLoop:
|
||||
for _, el := range context.Elements {
|
||||
switch clause := el.Clause.(type) {
|
||||
case *ArgClause:
|
||||
// Each new element should reset the previous state
|
||||
allSatisfied = false
|
||||
options = nil
|
||||
|
||||
if el.Value != nil && *el.Value != "" {
|
||||
// Get the list of valid options for the last argument
|
||||
validOptions := c.argGroup.args[argsSatisfied].resolveCompletions()
|
||||
if len(validOptions) == 0 {
|
||||
// If there are no options for this argument,
|
||||
// mark is as allSatisfied as we can't suggest anything
|
||||
if !clause.consumesRemainder() {
|
||||
argsSatisfied++
|
||||
allSatisfied = true
|
||||
}
|
||||
continue ElementLoop
|
||||
}
|
||||
|
||||
for _, opt := range validOptions {
|
||||
if opt == *el.Value {
|
||||
// We have an exact match
|
||||
// We don't need to suggest any option
|
||||
if !clause.consumesRemainder() {
|
||||
argsSatisfied++
|
||||
}
|
||||
continue ElementLoop
|
||||
}
|
||||
if strings.HasPrefix(opt, *el.Value) {
|
||||
// If the option match the partially entered argument, add it to the list
|
||||
options = append(options, opt)
|
||||
}
|
||||
}
|
||||
// Avoid further completion as we have done everything we could
|
||||
if !clause.consumesRemainder() {
|
||||
argsSatisfied++
|
||||
allSatisfied = true
|
||||
}
|
||||
}
|
||||
case *CmdClause:
|
||||
options = append(options, clause.completionAlts...)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
if argsSatisfied < len(c.argGroup.args) && !allSatisfied {
|
||||
// Since not all args have been satisfied, show options for the current one
|
||||
options = append(options, c.argGroup.args[argsSatisfied].resolveCompletions()...)
|
||||
} else {
|
||||
// If all args are satisfied, then go back to completing commands
|
||||
for _, cmd := range c.cmdGroup.commandOrder {
|
||||
if !cmd.hidden {
|
||||
options = append(options, cmd.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
func (c *cmdMixin) FlagCompletion(flagName string, flagValue string) (choices []string, flagMatch bool, optionMatch bool) {
|
||||
// Check if flagName matches a known flag.
|
||||
// If it does, show the options for the flag
|
||||
// Otherwise, show all flags
|
||||
|
||||
options := []string{}
|
||||
|
||||
for _, flag := range c.flagGroup.flagOrder {
|
||||
// Loop through each flag and determine if a match exists
|
||||
if flag.name == flagName {
|
||||
// User typed entire flag. Need to look for flag options.
|
||||
options = flag.resolveCompletions()
|
||||
if len(options) == 0 {
|
||||
// No Options to Choose From, Assume Match.
|
||||
return options, true, true
|
||||
}
|
||||
|
||||
// Loop options to find if the user specified value matches
|
||||
isPrefix := false
|
||||
matched := false
|
||||
|
||||
for _, opt := range options {
|
||||
if flagValue == opt {
|
||||
matched = true
|
||||
} else if strings.HasPrefix(opt, flagValue) {
|
||||
isPrefix = true
|
||||
}
|
||||
}
|
||||
|
||||
// Matched Flag Directly
|
||||
// Flag Value Not Prefixed, and Matched Directly
|
||||
return options, true, !isPrefix && matched
|
||||
}
|
||||
|
||||
if !flag.hidden {
|
||||
options = append(options, "--"+flag.name)
|
||||
}
|
||||
}
|
||||
// No Flag directly matched.
|
||||
return options, false, false
|
||||
|
||||
}
|
||||
|
||||
type cmdGroup struct {
|
||||
app *Application
|
||||
parent *CmdClause
|
||||
commands map[string]*CmdClause
|
||||
commandOrder []*CmdClause
|
||||
}
|
||||
|
||||
func (c *cmdGroup) defaultSubcommand() *CmdClause {
|
||||
for _, cmd := range c.commandOrder {
|
||||
if cmd.isDefault {
|
||||
return cmd
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cmdGroup) cmdNames() []string {
|
||||
names := make([]string, 0, len(c.commandOrder))
|
||||
for _, cmd := range c.commandOrder {
|
||||
names = append(names, cmd.name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// GetArg gets a command definition.
|
||||
//
|
||||
// This allows existing commands to be modified after definition but before parsing. Useful for
|
||||
// modular applications.
|
||||
func (c *cmdGroup) GetCommand(name string) *CmdClause {
|
||||
return c.commands[name]
|
||||
}
|
||||
|
||||
func newCmdGroup(app *Application) *cmdGroup {
|
||||
return &cmdGroup{
|
||||
app: app,
|
||||
commands: make(map[string]*CmdClause),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cmdGroup) flattenedCommands() (out []*CmdClause) {
|
||||
for _, cmd := range c.commandOrder {
|
||||
if len(cmd.commands) == 0 {
|
||||
out = append(out, cmd)
|
||||
}
|
||||
out = append(out, cmd.flattenedCommands()...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *cmdGroup) addCommand(name, help string) *CmdClause {
|
||||
cmd := newCommand(c.app, name, help)
|
||||
c.commands[name] = cmd
|
||||
c.commandOrder = append(c.commandOrder, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *cmdGroup) init() error {
|
||||
seen := map[string]bool{}
|
||||
if c.defaultSubcommand() != nil && !c.have() {
|
||||
return fmt.Errorf("default subcommand %q provided but no subcommands defined", c.defaultSubcommand().name)
|
||||
}
|
||||
defaults := []string{}
|
||||
for _, cmd := range c.commandOrder {
|
||||
if cmd.isDefault {
|
||||
defaults = append(defaults, cmd.name)
|
||||
}
|
||||
if seen[cmd.name] {
|
||||
return fmt.Errorf("duplicate command %q", cmd.name)
|
||||
}
|
||||
seen[cmd.name] = true
|
||||
for _, alias := range cmd.aliases {
|
||||
if seen[alias] {
|
||||
return fmt.Errorf("alias duplicates existing command %q", alias)
|
||||
}
|
||||
c.commands[alias] = cmd
|
||||
}
|
||||
if err := cmd.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(defaults) > 1 {
|
||||
return fmt.Errorf("more than one default subcommand exists: %s", strings.Join(defaults, ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cmdGroup) have() bool {
|
||||
return len(c.commands) > 0
|
||||
}
|
||||
|
||||
type CmdClauseValidator func(*CmdClause) error
|
||||
|
||||
// A CmdClause is a single top-level command. It encapsulates a set of flags
|
||||
// and either subcommands or positional arguments.
|
||||
type CmdClause struct {
|
||||
cmdMixin
|
||||
app *Application
|
||||
name string
|
||||
aliases []string
|
||||
help string
|
||||
helpLong string
|
||||
isDefault bool
|
||||
validator CmdClauseValidator
|
||||
hidden bool
|
||||
completionAlts []string
|
||||
}
|
||||
|
||||
func newCommand(app *Application, name, help string) *CmdClause {
|
||||
c := &CmdClause{
|
||||
app: app,
|
||||
name: name,
|
||||
help: help,
|
||||
}
|
||||
c.flagGroup = newFlagGroup()
|
||||
c.argGroup = newArgGroup()
|
||||
c.cmdGroup = newCmdGroup(app)
|
||||
return c
|
||||
}
|
||||
|
||||
// Add an Alias for this command.
|
||||
func (c *CmdClause) Alias(name string) *CmdClause {
|
||||
c.aliases = append(c.aliases, name)
|
||||
return c
|
||||
}
|
||||
|
||||
// Validate sets a validation function to run when parsing.
|
||||
func (c *CmdClause) Validate(validator CmdClauseValidator) *CmdClause {
|
||||
c.validator = validator
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CmdClause) FullCommand() string {
|
||||
out := []string{c.name}
|
||||
for p := c.parent; p != nil; p = p.parent {
|
||||
out = append([]string{p.name}, out...)
|
||||
}
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
|
||||
// Command adds a new sub-command.
|
||||
func (c *CmdClause) Command(name, help string) *CmdClause {
|
||||
cmd := c.addCommand(name, help)
|
||||
cmd.parent = c
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Default makes this command the default if commands don't match.
|
||||
func (c *CmdClause) Default() *CmdClause {
|
||||
c.isDefault = true
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CmdClause) Action(action Action) *CmdClause {
|
||||
c.addAction(action)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CmdClause) PreAction(action Action) *CmdClause {
|
||||
c.addPreAction(action)
|
||||
return c
|
||||
}
|
||||
|
||||
// Help sets the help message.
|
||||
func (c *CmdClause) Help(help string) *CmdClause {
|
||||
c.help = help
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CmdClause) init() error {
|
||||
if err := c.flagGroup.init(c.app.defaultEnvarPrefix()); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.argGroup.have() && c.cmdGroup.have() {
|
||||
return fmt.Errorf("can't mix Arg()s with Command()s")
|
||||
}
|
||||
if err := c.argGroup.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.cmdGroup.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CmdClause) Hidden() *CmdClause {
|
||||
c.hidden = true
|
||||
return c
|
||||
}
|
||||
|
||||
// HelpLong adds a long help text, which can be used in usage templates.
|
||||
// For example, to use a longer help text in the command-specific help
|
||||
// than in the apps root help.
|
||||
func (c *CmdClause) HelpLong(help string) *CmdClause {
|
||||
c.helpLong = help
|
||||
return c
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package kingpin
|
||||
|
||||
// HintAction is a function type who is expected to return a slice of possible
|
||||
// command line arguments.
|
||||
type HintAction func() []string
|
||||
type completionsMixin struct {
|
||||
hintActions []HintAction
|
||||
builtinHintActions []HintAction
|
||||
}
|
||||
|
||||
func (a *completionsMixin) addHintAction(action HintAction) {
|
||||
a.hintActions = append(a.hintActions, action)
|
||||
}
|
||||
|
||||
// Allow adding of HintActions which are added internally, ie, EnumVar
|
||||
func (a *completionsMixin) addHintActionBuiltin(action HintAction) {
|
||||
a.builtinHintActions = append(a.builtinHintActions, action)
|
||||
}
|
||||
|
||||
func (a *completionsMixin) resolveCompletions() []string {
|
||||
var hints []string
|
||||
|
||||
options := a.builtinHintActions
|
||||
if len(a.hintActions) > 0 {
|
||||
// User specified their own hintActions. Use those instead.
|
||||
options = a.hintActions
|
||||
}
|
||||
|
||||
for _, hintAction := range options {
|
||||
hints = append(hints, hintAction()...)
|
||||
}
|
||||
return hints
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
// Package kingpin provides command line interfaces like this:
|
||||
//
|
||||
// $ chat
|
||||
// usage: chat [<flags>] <command> [<flags>] [<args> ...]
|
||||
//
|
||||
// Flags:
|
||||
// --debug enable debug mode
|
||||
// --help Show help.
|
||||
// --server=127.0.0.1 server address
|
||||
//
|
||||
// Commands:
|
||||
// help <command>
|
||||
// Show help for a command.
|
||||
//
|
||||
// post [<flags>] <channel>
|
||||
// Post a message to a channel.
|
||||
//
|
||||
// register <nick> <name>
|
||||
// Register a new user.
|
||||
//
|
||||
// $ chat help post
|
||||
// usage: chat [<flags>] post [<flags>] <channel> [<text>]
|
||||
//
|
||||
// Post a message to a channel.
|
||||
//
|
||||
// Flags:
|
||||
// --image=IMAGE image to post
|
||||
//
|
||||
// Args:
|
||||
// <channel> channel to post to
|
||||
// [<text>] text to post
|
||||
// $ chat post --image=~/Downloads/owls.jpg pics
|
||||
//
|
||||
// From code like this:
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// import "github.com/alecthomas/kingpin/v2"
|
||||
//
|
||||
// var (
|
||||
// debug = kingpin.Flag("debug", "enable debug mode").Default("false").Bool()
|
||||
// serverIP = kingpin.Flag("server", "server address").Default("127.0.0.1").IP()
|
||||
//
|
||||
// register = kingpin.Command("register", "Register a new user.")
|
||||
// registerNick = register.Arg("nick", "nickname for user").Required().String()
|
||||
// registerName = register.Arg("name", "name of user").Required().String()
|
||||
//
|
||||
// post = kingpin.Command("post", "Post a message to a channel.")
|
||||
// postImage = post.Flag("image", "image to post").ExistingFile()
|
||||
// postChannel = post.Arg("channel", "channel to post to").Required().String()
|
||||
// postText = post.Arg("text", "text to post").String()
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
// switch kingpin.Parse() {
|
||||
// // Register user
|
||||
// case "register":
|
||||
// println(*registerNick)
|
||||
//
|
||||
// // Post message
|
||||
// case "post":
|
||||
// if *postImage != nil {
|
||||
// }
|
||||
// if *postText != "" {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
package kingpin
|
|
@ -0,0 +1,40 @@
|
|||
package kingpin
|
||||
|
||||
import (
|
||||
"os"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var (
|
||||
envVarValuesSeparator = "\r?\n"
|
||||
envVarValuesTrimmer = regexp.MustCompile(envVarValuesSeparator + "$")
|
||||
envVarValuesSplitter = regexp.MustCompile(envVarValuesSeparator)
|
||||
)
|
||||
|
||||
type envarMixin struct {
|
||||
envar string
|
||||
noEnvar bool
|
||||
}
|
||||
|
||||
func (e *envarMixin) HasEnvarValue() bool {
|
||||
return e.GetEnvarValue() != ""
|
||||
}
|
||||
|
||||
func (e *envarMixin) GetEnvarValue() string {
|
||||
if e.noEnvar || e.envar == "" {
|
||||
return ""
|
||||
}
|
||||
return os.Getenv(e.envar)
|
||||
}
|
||||
|
||||
func (e *envarMixin) GetSplitEnvarValue() []string {
|
||||
envarValue := e.GetEnvarValue()
|
||||
if envarValue == "" {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// Split by new line to extract multiple values, if any.
|
||||
trimmed := envVarValuesTrimmer.ReplaceAllString(envarValue, "")
|
||||
|
||||
return envVarValuesSplitter.Split(trimmed, -1)
|
||||
}
|
|
@ -0,0 +1,332 @@
|
|||
package kingpin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type flagGroup struct {
|
||||
short map[string]*FlagClause
|
||||
long map[string]*FlagClause
|
||||
flagOrder []*FlagClause
|
||||
}
|
||||
|
||||
func newFlagGroup() *flagGroup {
|
||||
return &flagGroup{
|
||||
short: map[string]*FlagClause{},
|
||||
long: map[string]*FlagClause{},
|
||||
}
|
||||
}
|
||||
|
||||
// GetFlag gets a flag definition.
|
||||
//
|
||||
// This allows existing flags to be modified after definition but before parsing. Useful for
|
||||
// modular applications.
|
||||
func (f *flagGroup) GetFlag(name string) *FlagClause {
|
||||
return f.long[name]
|
||||
}
|
||||
|
||||
// Flag defines a new flag with the given long name and help.
|
||||
func (f *flagGroup) Flag(name, help string) *FlagClause {
|
||||
flag := newFlag(name, help)
|
||||
f.long[name] = flag
|
||||
f.flagOrder = append(f.flagOrder, flag)
|
||||
return flag
|
||||
}
|
||||
|
||||
func (f *flagGroup) init(defaultEnvarPrefix string) error {
|
||||
if err := f.checkDuplicates(); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, flag := range f.long {
|
||||
if defaultEnvarPrefix != "" && !flag.noEnvar && flag.envar == "" {
|
||||
flag.envar = envarTransform(defaultEnvarPrefix + "_" + flag.name)
|
||||
}
|
||||
if err := flag.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
if flag.shorthand != 0 {
|
||||
f.short[string(flag.shorthand)] = flag
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *flagGroup) checkDuplicates() error {
|
||||
seenShort := map[rune]bool{}
|
||||
seenLong := map[string]bool{}
|
||||
for _, flag := range f.flagOrder {
|
||||
if flag.shorthand != 0 {
|
||||
if _, ok := seenShort[flag.shorthand]; ok {
|
||||
return fmt.Errorf("duplicate short flag -%c", flag.shorthand)
|
||||
}
|
||||
seenShort[flag.shorthand] = true
|
||||
}
|
||||
if _, ok := seenLong[flag.name]; ok {
|
||||
return fmt.Errorf("duplicate long flag --%s", flag.name)
|
||||
}
|
||||
seenLong[flag.name] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *flagGroup) parse(context *ParseContext) (*FlagClause, error) {
|
||||
var token *Token
|
||||
|
||||
loop:
|
||||
for {
|
||||
token = context.Peek()
|
||||
switch token.Type {
|
||||
case TokenEOL:
|
||||
break loop
|
||||
|
||||
case TokenLong, TokenShort:
|
||||
flagToken := token
|
||||
defaultValue := ""
|
||||
var flag *FlagClause
|
||||
var ok bool
|
||||
invert := false
|
||||
|
||||
name := token.Value
|
||||
if token.Type == TokenLong {
|
||||
flag, ok = f.long[name]
|
||||
if !ok {
|
||||
if strings.HasPrefix(name, "no-") {
|
||||
name = name[3:]
|
||||
invert = true
|
||||
}
|
||||
flag, ok = f.long[name]
|
||||
}
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown long flag '%s'", flagToken)
|
||||
}
|
||||
} else {
|
||||
flag, ok = f.short[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown short flag '%s'", flagToken)
|
||||
}
|
||||
}
|
||||
|
||||
context.Next()
|
||||
|
||||
flag.isSetByUser()
|
||||
|
||||
fb, ok := flag.value.(boolFlag)
|
||||
if ok && fb.IsBoolFlag() {
|
||||
if invert {
|
||||
defaultValue = "false"
|
||||
} else {
|
||||
defaultValue = "true"
|
||||
}
|
||||
} else {
|
||||
if invert {
|
||||
context.Push(token)
|
||||
return nil, fmt.Errorf("unknown long flag '%s'", flagToken)
|
||||
}
|
||||
token = context.Peek()
|
||||
if token.Type != TokenArg {
|
||||
context.Push(token)
|
||||
return nil, fmt.Errorf("expected argument for flag '%s'", flagToken)
|
||||
}
|
||||
context.Next()
|
||||
defaultValue = token.Value
|
||||
}
|
||||
|
||||
context.matchedFlag(flag, defaultValue)
|
||||
return flag, nil
|
||||
|
||||
default:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// FlagClause is a fluid interface used to build flags.
|
||||
type FlagClause struct {
|
||||
parserMixin
|
||||
actionMixin
|
||||
completionsMixin
|
||||
envarMixin
|
||||
name string
|
||||
shorthand rune
|
||||
help string
|
||||
defaultValues []string
|
||||
placeholder string
|
||||
hidden bool
|
||||
setByUser *bool
|
||||
}
|
||||
|
||||
func newFlag(name, help string) *FlagClause {
|
||||
f := &FlagClause{
|
||||
name: name,
|
||||
help: help,
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *FlagClause) setDefault() error {
|
||||
if f.HasEnvarValue() {
|
||||
if v, ok := f.value.(repeatableFlag); !ok || !v.IsCumulative() {
|
||||
// Use the value as-is
|
||||
return f.value.Set(f.GetEnvarValue())
|
||||
} else {
|
||||
for _, value := range f.GetSplitEnvarValue() {
|
||||
if err := f.value.Set(value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(f.defaultValues) > 0 {
|
||||
for _, defaultValue := range f.defaultValues {
|
||||
if err := f.value.Set(defaultValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FlagClause) isSetByUser() {
|
||||
if f.setByUser != nil {
|
||||
*f.setByUser = true
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FlagClause) needsValue() bool {
|
||||
haveDefault := len(f.defaultValues) > 0
|
||||
return f.required && !(haveDefault || f.HasEnvarValue())
|
||||
}
|
||||
|
||||
func (f *FlagClause) init() error {
|
||||
if f.required && len(f.defaultValues) > 0 {
|
||||
return fmt.Errorf("required flag '--%s' with default value that will never be used", f.name)
|
||||
}
|
||||
if f.value == nil {
|
||||
return fmt.Errorf("no type defined for --%s (eg. .String())", f.name)
|
||||
}
|
||||
if v, ok := f.value.(repeatableFlag); (!ok || !v.IsCumulative()) && len(f.defaultValues) > 1 {
|
||||
return fmt.Errorf("invalid default for '--%s', expecting single value", f.name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Dispatch to the given function after the flag is parsed and validated.
|
||||
func (f *FlagClause) Action(action Action) *FlagClause {
|
||||
f.addAction(action)
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *FlagClause) PreAction(action Action) *FlagClause {
|
||||
f.addPreAction(action)
|
||||
return f
|
||||
}
|
||||
|
||||
// HintAction registers a HintAction (function) for the flag to provide completions
|
||||
func (a *FlagClause) HintAction(action HintAction) *FlagClause {
|
||||
a.addHintAction(action)
|
||||
return a
|
||||
}
|
||||
|
||||
// HintOptions registers any number of options for the flag to provide completions
|
||||
func (a *FlagClause) HintOptions(options ...string) *FlagClause {
|
||||
a.addHintAction(func() []string {
|
||||
return options
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *FlagClause) EnumVar(target *string, options ...string) {
|
||||
a.parserMixin.EnumVar(target, options...)
|
||||
a.addHintActionBuiltin(func() []string {
|
||||
return options
|
||||
})
|
||||
}
|
||||
|
||||
func (a *FlagClause) Enum(options ...string) (target *string) {
|
||||
a.addHintActionBuiltin(func() []string {
|
||||
return options
|
||||
})
|
||||
return a.parserMixin.Enum(options...)
|
||||
}
|
||||
|
||||
// IsSetByUser let to know if the flag was set by the user
|
||||
func (f *FlagClause) IsSetByUser(setByUser *bool) *FlagClause {
|
||||
if setByUser != nil {
|
||||
*setByUser = false
|
||||
}
|
||||
f.setByUser = setByUser
|
||||
return f
|
||||
}
|
||||
|
||||
// Default values for this flag. They *must* be parseable by the value of the flag.
|
||||
func (f *FlagClause) Default(values ...string) *FlagClause {
|
||||
f.defaultValues = values
|
||||
return f
|
||||
}
|
||||
|
||||
// DEPRECATED: Use Envar(name) instead.
|
||||
func (f *FlagClause) OverrideDefaultFromEnvar(envar string) *FlagClause {
|
||||
return f.Envar(envar)
|
||||
}
|
||||
|
||||
// Envar overrides the default value(s) for a flag from an environment variable,
|
||||
// if it is set. Several default values can be provided by using new lines to
|
||||
// separate them.
|
||||
func (f *FlagClause) Envar(name string) *FlagClause {
|
||||
f.envar = name
|
||||
f.noEnvar = false
|
||||
return f
|
||||
}
|
||||
|
||||
// NoEnvar forces environment variable defaults to be disabled for this flag.
|
||||
// Most useful in conjunction with app.DefaultEnvars().
|
||||
func (f *FlagClause) NoEnvar() *FlagClause {
|
||||
f.envar = ""
|
||||
f.noEnvar = true
|
||||
return f
|
||||
}
|
||||
|
||||
// PlaceHolder sets the place-holder string used for flag values in the help. The
|
||||
// default behaviour is to use the value provided by Default() if provided,
|
||||
// then fall back on the capitalized flag name.
|
||||
func (f *FlagClause) PlaceHolder(placeholder string) *FlagClause {
|
||||
f.placeholder = placeholder
|
||||
return f
|
||||
}
|
||||
|
||||
// Hidden hides a flag from usage but still allows it to be used.
|
||||
func (f *FlagClause) Hidden() *FlagClause {
|
||||
f.hidden = true
|
||||
return f
|
||||
}
|
||||
|
||||
// Required makes the flag required. You can not provide a Default() value to a Required() flag.
|
||||
func (f *FlagClause) Required() *FlagClause {
|
||||
f.required = true
|
||||
return f
|
||||
}
|
||||
|
||||
// Short sets the short flag name.
|
||||
func (f *FlagClause) Short(name rune) *FlagClause {
|
||||
f.shorthand = name
|
||||
return f
|
||||
}
|
||||
|
||||
// Help sets the help message.
|
||||
func (f *FlagClause) Help(help string) *FlagClause {
|
||||
f.help = help
|
||||
return f
|
||||
}
|
||||
|
||||
// Bool makes this flag a boolean flag.
|
||||
func (f *FlagClause) Bool() (target *bool) {
|
||||
target = new(bool)
|
||||
f.SetValue(newBoolValue(target))
|
||||
return
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package kingpin
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var (
|
||||
// CommandLine is the default Kingpin parser.
|
||||
CommandLine = New(filepath.Base(os.Args[0]), "")
|
||||
// Global help flag. Exposed for user customisation.
|
||||
HelpFlag = CommandLine.HelpFlag
|
||||
// Top-level help command. Exposed for user customisation. May be nil.
|
||||
HelpCommand = CommandLine.HelpCommand
|
||||
// Global version flag. Exposed for user customisation. May be nil.
|
||||
VersionFlag = CommandLine.VersionFlag
|
||||
// Whether to file expansion with '@' is enabled.
|
||||
EnableFileExpansion = true
|
||||
)
|
||||
|
||||
// Command adds a new command to the default parser.
|
||||
func Command(name, help string) *CmdClause {
|
||||
return CommandLine.Command(name, help)
|
||||
}
|
||||
|
||||
// Flag adds a new flag to the default parser.
|
||||
func Flag(name, help string) *FlagClause {
|
||||
return CommandLine.Flag(name, help)
|
||||
}
|
||||
|
||||
// Arg adds a new argument to the top-level of the default parser.
|
||||
func Arg(name, help string) *ArgClause {
|
||||
return CommandLine.Arg(name, help)
|
||||
}
|
||||
|
||||
// Parse and return the selected command. Will call the termination handler if
|
||||
// an error is encountered.
|
||||
func Parse() string {
|
||||
selected := MustParse(CommandLine.Parse(os.Args[1:]))
|
||||
if selected == "" && CommandLine.cmdGroup.have() {
|
||||
Usage()
|
||||
CommandLine.terminate(0)
|
||||
}
|
||||
return selected
|
||||
}
|
||||
|
||||
// Errorf prints an error message to stderr.
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
CommandLine.Errorf(format, args...)
|
||||
}
|
||||
|
||||
// Fatalf prints an error message to stderr and exits.
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
CommandLine.Fatalf(format, args...)
|
||||
}
|
||||
|
||||
// FatalIfError prints an error and exits if err is not nil. The error is printed
|
||||
// with the given prefix.
|
||||
func FatalIfError(err error, format string, args ...interface{}) {
|
||||
CommandLine.FatalIfError(err, format, args...)
|
||||
}
|
||||
|
||||
// FatalUsage prints an error message followed by usage information, then
|
||||
// exits with a non-zero status.
|
||||
func FatalUsage(format string, args ...interface{}) {
|
||||
CommandLine.FatalUsage(format, args...)
|
||||
}
|
||||
|
||||
// FatalUsageContext writes a printf formatted error message to stderr, then
|
||||
// usage information for the given ParseContext, before exiting.
|
||||
func FatalUsageContext(context *ParseContext, format string, args ...interface{}) {
|
||||
CommandLine.FatalUsageContext(context, format, args...)
|
||||
}
|
||||
|
||||
// Usage prints usage to stderr.
|
||||
func Usage() {
|
||||
CommandLine.Usage(os.Args[1:])
|
||||
}
|
||||
|
||||
// Set global usage template to use (defaults to DefaultUsageTemplate).
|
||||
func UsageTemplate(template string) *Application {
|
||||
return CommandLine.UsageTemplate(template)
|
||||
}
|
||||
|
||||
// MustParse can be used with app.Parse(args) to exit with an error if parsing fails.
|
||||
func MustParse(command string, err error) string {
|
||||
if err != nil {
|
||||
Fatalf("%s, try --help", err)
|
||||
}
|
||||
return command
|
||||
}
|
||||
|
||||
// Version adds a flag for displaying the application version number.
|
||||
func Version(version string) *Application {
|
||||
return CommandLine.Version(version)
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
// +build appengine !linux,!freebsd,!darwin,!dragonfly,!netbsd,!openbsd
|
||||
|
||||
package kingpin
|
||||
|
||||
import "io"
|
||||
|
||||
func guessWidth(w io.Writer) int {
|
||||
return 80
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// +build !appengine,linux freebsd darwin dragonfly netbsd openbsd
|
||||
|
||||
package kingpin
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func guessWidth(w io.Writer) int {
|
||||
// check if COLUMNS env is set to comply with
|
||||
// http://pubs.opengroup.org/onlinepubs/009604499/basedefs/xbd_chap08.html
|
||||
colsStr := os.Getenv("COLUMNS")
|
||||
if colsStr != "" {
|
||||
if cols, err := strconv.Atoi(colsStr); err == nil {
|
||||
return cols
|
||||
}
|
||||
}
|
||||
|
||||
if t, ok := w.(*os.File); ok {
|
||||
fd := t.Fd()
|
||||
var dimensions [4]uint16
|
||||
|
||||
if _, _, err := syscall.Syscall6(
|
||||
syscall.SYS_IOCTL,
|
||||
uintptr(fd),
|
||||
uintptr(syscall.TIOCGWINSZ),
|
||||
uintptr(unsafe.Pointer(&dimensions)),
|
||||
0, 0, 0,
|
||||
); err == 0 {
|
||||
return int(dimensions[1])
|
||||
}
|
||||
}
|
||||
return 80
|
||||
}
|
|
@ -0,0 +1,273 @@
|
|||
package kingpin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Data model for Kingpin command-line structure.
|
||||
|
||||
var (
|
||||
ignoreInCount = map[string]bool{
|
||||
"help": true,
|
||||
"help-long": true,
|
||||
"help-man": true,
|
||||
"completion-bash": true,
|
||||
"completion-script-bash": true,
|
||||
"completion-script-zsh": true,
|
||||
}
|
||||
)
|
||||
|
||||
type FlagGroupModel struct {
|
||||
Flags []*FlagModel
|
||||
}
|
||||
|
||||
func (f *FlagGroupModel) FlagSummary() string {
|
||||
out := []string{}
|
||||
count := 0
|
||||
|
||||
for _, flag := range f.Flags {
|
||||
|
||||
if !ignoreInCount[flag.Name] {
|
||||
count++
|
||||
}
|
||||
|
||||
if flag.Required {
|
||||
if flag.IsBoolFlag() {
|
||||
out = append(out, fmt.Sprintf("--[no-]%s", flag.Name))
|
||||
} else {
|
||||
out = append(out, fmt.Sprintf("--%s=%s", flag.Name, flag.FormatPlaceHolder()))
|
||||
}
|
||||
}
|
||||
}
|
||||
if count != len(out) {
|
||||
out = append(out, "[<flags>]")
|
||||
}
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
|
||||
type FlagModel struct {
|
||||
Name string
|
||||
Help string
|
||||
Short rune
|
||||
Default []string
|
||||
Envar string
|
||||
PlaceHolder string
|
||||
Required bool
|
||||
Hidden bool
|
||||
Value Value
|
||||
}
|
||||
|
||||
func (f *FlagModel) String() string {
|
||||
if f.Value == nil {
|
||||
return ""
|
||||
}
|
||||
return f.Value.String()
|
||||
}
|
||||
|
||||
func (f *FlagModel) IsBoolFlag() bool {
|
||||
if fl, ok := f.Value.(boolFlag); ok {
|
||||
return fl.IsBoolFlag()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *FlagModel) FormatPlaceHolder() string {
|
||||
if f.PlaceHolder != "" {
|
||||
return f.PlaceHolder
|
||||
}
|
||||
if len(f.Default) > 0 {
|
||||
ellipsis := ""
|
||||
if len(f.Default) > 1 {
|
||||
ellipsis = "..."
|
||||
}
|
||||
if _, ok := f.Value.(*stringValue); ok {
|
||||
return strconv.Quote(f.Default[0]) + ellipsis
|
||||
}
|
||||
return f.Default[0] + ellipsis
|
||||
}
|
||||
return strings.ToUpper(f.Name)
|
||||
}
|
||||
|
||||
func (f *FlagModel) HelpWithEnvar() string {
|
||||
if f.Envar == "" {
|
||||
return f.Help
|
||||
}
|
||||
return fmt.Sprintf("%s ($%s)", f.Help, f.Envar)
|
||||
}
|
||||
|
||||
type ArgGroupModel struct {
|
||||
Args []*ArgModel
|
||||
}
|
||||
|
||||
func (a *ArgGroupModel) ArgSummary() string {
|
||||
depth := 0
|
||||
out := []string{}
|
||||
for _, arg := range a.Args {
|
||||
var h string
|
||||
if arg.PlaceHolder != "" {
|
||||
h = arg.PlaceHolder
|
||||
} else {
|
||||
h = "<" + arg.Name + ">"
|
||||
}
|
||||
if !arg.Required {
|
||||
h = "[" + h
|
||||
depth++
|
||||
}
|
||||
out = append(out, h)
|
||||
}
|
||||
out[len(out)-1] = out[len(out)-1] + strings.Repeat("]", depth)
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
|
||||
func (a *ArgModel) HelpWithEnvar() string {
|
||||
if a.Envar == "" {
|
||||
return a.Help
|
||||
}
|
||||
return fmt.Sprintf("%s ($%s)", a.Help, a.Envar)
|
||||
}
|
||||
|
||||
type ArgModel struct {
|
||||
Name string
|
||||
Help string
|
||||
Default []string
|
||||
Envar string
|
||||
PlaceHolder string
|
||||
Required bool
|
||||
Hidden bool
|
||||
Value Value
|
||||
}
|
||||
|
||||
func (a *ArgModel) String() string {
|
||||
if a.Value == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return a.Value.String()
|
||||
}
|
||||
|
||||
type CmdGroupModel struct {
|
||||
Commands []*CmdModel
|
||||
}
|
||||
|
||||
func (c *CmdGroupModel) FlattenedCommands() (out []*CmdModel) {
|
||||
for _, cmd := range c.Commands {
|
||||
if len(cmd.Commands) == 0 {
|
||||
out = append(out, cmd)
|
||||
}
|
||||
out = append(out, cmd.FlattenedCommands()...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type CmdModel struct {
|
||||
Name string
|
||||
Aliases []string
|
||||
Help string
|
||||
HelpLong string
|
||||
FullCommand string
|
||||
Depth int
|
||||
Hidden bool
|
||||
Default bool
|
||||
*FlagGroupModel
|
||||
*ArgGroupModel
|
||||
*CmdGroupModel
|
||||
}
|
||||
|
||||
func (c *CmdModel) String() string {
|
||||
return c.FullCommand
|
||||
}
|
||||
|
||||
type ApplicationModel struct {
|
||||
Name string
|
||||
Help string
|
||||
Version string
|
||||
Author string
|
||||
*ArgGroupModel
|
||||
*CmdGroupModel
|
||||
*FlagGroupModel
|
||||
}
|
||||
|
||||
func (a *Application) Model() *ApplicationModel {
|
||||
return &ApplicationModel{
|
||||
Name: a.Name,
|
||||
Help: a.Help,
|
||||
Version: a.version,
|
||||
Author: a.author,
|
||||
FlagGroupModel: a.flagGroup.Model(),
|
||||
ArgGroupModel: a.argGroup.Model(),
|
||||
CmdGroupModel: a.cmdGroup.Model(),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *argGroup) Model() *ArgGroupModel {
|
||||
m := &ArgGroupModel{}
|
||||
for _, arg := range a.args {
|
||||
m.Args = append(m.Args, arg.Model())
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (a *ArgClause) Model() *ArgModel {
|
||||
return &ArgModel{
|
||||
Name: a.name,
|
||||
Help: a.help,
|
||||
Default: a.defaultValues,
|
||||
Envar: a.envar,
|
||||
PlaceHolder: a.placeholder,
|
||||
Required: a.required,
|
||||
Hidden: a.hidden,
|
||||
Value: a.value,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *flagGroup) Model() *FlagGroupModel {
|
||||
m := &FlagGroupModel{}
|
||||
for _, fl := range f.flagOrder {
|
||||
m.Flags = append(m.Flags, fl.Model())
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (f *FlagClause) Model() *FlagModel {
|
||||
return &FlagModel{
|
||||
Name: f.name,
|
||||
Help: f.help,
|
||||
Short: rune(f.shorthand),
|
||||
Default: f.defaultValues,
|
||||
Envar: f.envar,
|
||||
PlaceHolder: f.placeholder,
|
||||
Required: f.required,
|
||||
Hidden: f.hidden,
|
||||
Value: f.value,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cmdGroup) Model() *CmdGroupModel {
|
||||
m := &CmdGroupModel{}
|
||||
for _, cm := range c.commandOrder {
|
||||
m.Commands = append(m.Commands, cm.Model())
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (c *CmdClause) Model() *CmdModel {
|
||||
depth := 0
|
||||
for i := c; i != nil; i = i.parent {
|
||||
depth++
|
||||
}
|
||||
return &CmdModel{
|
||||
Name: c.name,
|
||||
Aliases: c.aliases,
|
||||
Help: c.help,
|
||||
HelpLong: c.helpLong,
|
||||
Depth: depth,
|
||||
Hidden: c.hidden,
|
||||
Default: c.isDefault,
|
||||
FullCommand: c.FullCommand(),
|
||||
FlagGroupModel: c.flagGroup.Model(),
|
||||
ArgGroupModel: c.argGroup.Model(),
|
||||
CmdGroupModel: c.cmdGroup.Model(),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,396 @@
|
|||
package kingpin
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type TokenType int
|
||||
|
||||
// Token types.
|
||||
const (
|
||||
TokenShort TokenType = iota
|
||||
TokenLong
|
||||
TokenArg
|
||||
TokenError
|
||||
TokenEOL
|
||||
)
|
||||
|
||||
func (t TokenType) String() string {
|
||||
switch t {
|
||||
case TokenShort:
|
||||
return "short flag"
|
||||
case TokenLong:
|
||||
return "long flag"
|
||||
case TokenArg:
|
||||
return "argument"
|
||||
case TokenError:
|
||||
return "error"
|
||||
case TokenEOL:
|
||||
return "<EOL>"
|
||||
}
|
||||
return "?"
|
||||
}
|
||||
|
||||
var (
|
||||
TokenEOLMarker = Token{-1, TokenEOL, ""}
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
Index int
|
||||
Type TokenType
|
||||
Value string
|
||||
}
|
||||
|
||||
func (t *Token) Equal(o *Token) bool {
|
||||
return t.Index == o.Index
|
||||
}
|
||||
|
||||
func (t *Token) IsFlag() bool {
|
||||
return t.Type == TokenShort || t.Type == TokenLong
|
||||
}
|
||||
|
||||
func (t *Token) IsEOF() bool {
|
||||
return t.Type == TokenEOL
|
||||
}
|
||||
|
||||
func (t *Token) String() string {
|
||||
switch t.Type {
|
||||
case TokenShort:
|
||||
return "-" + t.Value
|
||||
case TokenLong:
|
||||
return "--" + t.Value
|
||||
case TokenArg:
|
||||
return t.Value
|
||||
case TokenError:
|
||||
return "error: " + t.Value
|
||||
case TokenEOL:
|
||||
return "<EOL>"
|
||||
default:
|
||||
panic("unhandled type")
|
||||
}
|
||||
}
|
||||
|
||||
// A union of possible elements in a parse stack.
|
||||
type ParseElement struct {
|
||||
// Clause is either *CmdClause, *ArgClause or *FlagClause.
|
||||
Clause interface{}
|
||||
// Value is corresponding value for an ArgClause or FlagClause (if any).
|
||||
Value *string
|
||||
}
|
||||
|
||||
// ParseContext holds the current context of the parser. When passed to
|
||||
// Action() callbacks Elements will be fully populated with *FlagClause,
|
||||
// *ArgClause and *CmdClause values and their corresponding arguments (if
|
||||
// any).
|
||||
type ParseContext struct {
|
||||
SelectedCommand *CmdClause
|
||||
ignoreDefault bool
|
||||
argsOnly bool
|
||||
peek []*Token
|
||||
argi int // Index of current command-line arg we're processing.
|
||||
args []string
|
||||
rawArgs []string
|
||||
flags *flagGroup
|
||||
arguments *argGroup
|
||||
argumenti int // Cursor into arguments
|
||||
// Flags, arguments and commands encountered and collected during parse.
|
||||
Elements []*ParseElement
|
||||
}
|
||||
|
||||
func (p *ParseContext) nextArg() *ArgClause {
|
||||
if p.argumenti >= len(p.arguments.args) {
|
||||
return nil
|
||||
}
|
||||
arg := p.arguments.args[p.argumenti]
|
||||
if !arg.consumesRemainder() {
|
||||
p.argumenti++
|
||||
}
|
||||
return arg
|
||||
}
|
||||
|
||||
func (p *ParseContext) next() {
|
||||
p.argi++
|
||||
p.args = p.args[1:]
|
||||
}
|
||||
|
||||
// HasTrailingArgs returns true if there are unparsed command-line arguments.
|
||||
// This can occur if the parser can not match remaining arguments.
|
||||
func (p *ParseContext) HasTrailingArgs() bool {
|
||||
return len(p.args) > 0
|
||||
}
|
||||
|
||||
func tokenize(args []string, ignoreDefault bool) *ParseContext {
|
||||
return &ParseContext{
|
||||
ignoreDefault: ignoreDefault,
|
||||
args: args,
|
||||
rawArgs: args,
|
||||
flags: newFlagGroup(),
|
||||
arguments: newArgGroup(),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ParseContext) mergeFlags(flags *flagGroup) {
|
||||
for _, flag := range flags.flagOrder {
|
||||
if flag.shorthand != 0 {
|
||||
p.flags.short[string(flag.shorthand)] = flag
|
||||
}
|
||||
p.flags.long[flag.name] = flag
|
||||
p.flags.flagOrder = append(p.flags.flagOrder, flag)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ParseContext) mergeArgs(args *argGroup) {
|
||||
p.arguments.args = append(p.arguments.args, args.args...)
|
||||
}
|
||||
|
||||
func (p *ParseContext) EOL() bool {
|
||||
return p.Peek().Type == TokenEOL
|
||||
}
|
||||
|
||||
func (p *ParseContext) Error() bool {
|
||||
return p.Peek().Type == TokenError
|
||||
}
|
||||
|
||||
// Next token in the parse context.
|
||||
func (p *ParseContext) Next() *Token {
|
||||
if len(p.peek) > 0 {
|
||||
return p.pop()
|
||||
}
|
||||
|
||||
// End of tokens.
|
||||
if len(p.args) == 0 {
|
||||
return &Token{Index: p.argi, Type: TokenEOL}
|
||||
}
|
||||
|
||||
if p.argi > 0 && p.argi <= len(p.rawArgs) && p.rawArgs[p.argi-1] == "--" {
|
||||
// If the previous argument was a --, from now on only arguments are parsed.
|
||||
p.argsOnly = true
|
||||
}
|
||||
arg := p.args[0]
|
||||
p.next()
|
||||
|
||||
if p.argsOnly {
|
||||
return &Token{p.argi, TokenArg, arg}
|
||||
}
|
||||
|
||||
if arg == "--" {
|
||||
return p.Next()
|
||||
}
|
||||
|
||||
if strings.HasPrefix(arg, "--") {
|
||||
parts := strings.SplitN(arg[2:], "=", 2)
|
||||
token := &Token{p.argi, TokenLong, parts[0]}
|
||||
if len(parts) == 2 {
|
||||
p.Push(&Token{p.argi, TokenArg, parts[1]})
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
if strings.HasPrefix(arg, "-") {
|
||||
if len(arg) == 1 {
|
||||
return &Token{Index: p.argi, Type: TokenArg}
|
||||
}
|
||||
shortRune, size := utf8.DecodeRuneInString(arg[1:])
|
||||
short := string(shortRune)
|
||||
flag, ok := p.flags.short[short]
|
||||
// Not a known short flag, we'll just return it anyway.
|
||||
if !ok {
|
||||
} else if fb, ok := flag.value.(boolFlag); ok && fb.IsBoolFlag() {
|
||||
// Bool short flag.
|
||||
} else {
|
||||
// Short flag with combined argument: -fARG
|
||||
token := &Token{p.argi, TokenShort, short}
|
||||
if len(arg) > size+1 {
|
||||
p.Push(&Token{p.argi, TokenArg, arg[size+1:]})
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
if len(arg) > size+1 {
|
||||
p.args = append([]string{"-" + arg[size+1:]}, p.args...)
|
||||
}
|
||||
return &Token{p.argi, TokenShort, short}
|
||||
} else if EnableFileExpansion && strings.HasPrefix(arg, "@") {
|
||||
expanded, err := ExpandArgsFromFile(arg[1:])
|
||||
if err != nil {
|
||||
return &Token{p.argi, TokenError, err.Error()}
|
||||
}
|
||||
if len(p.args) == 0 {
|
||||
p.args = expanded
|
||||
} else {
|
||||
p.args = append(expanded, p.args...)
|
||||
}
|
||||
return p.Next()
|
||||
}
|
||||
|
||||
return &Token{p.argi, TokenArg, arg}
|
||||
}
|
||||
|
||||
func (p *ParseContext) Peek() *Token {
|
||||
if len(p.peek) == 0 {
|
||||
return p.Push(p.Next())
|
||||
}
|
||||
return p.peek[len(p.peek)-1]
|
||||
}
|
||||
|
||||
func (p *ParseContext) Push(token *Token) *Token {
|
||||
p.peek = append(p.peek, token)
|
||||
return token
|
||||
}
|
||||
|
||||
func (p *ParseContext) pop() *Token {
|
||||
end := len(p.peek) - 1
|
||||
token := p.peek[end]
|
||||
p.peek = p.peek[0:end]
|
||||
return token
|
||||
}
|
||||
|
||||
func (p *ParseContext) String() string {
|
||||
return p.SelectedCommand.FullCommand()
|
||||
}
|
||||
|
||||
func (p *ParseContext) matchedFlag(flag *FlagClause, value string) {
|
||||
p.Elements = append(p.Elements, &ParseElement{Clause: flag, Value: &value})
|
||||
}
|
||||
|
||||
func (p *ParseContext) matchedArg(arg *ArgClause, value string) {
|
||||
p.Elements = append(p.Elements, &ParseElement{Clause: arg, Value: &value})
|
||||
}
|
||||
|
||||
func (p *ParseContext) matchedCmd(cmd *CmdClause) {
|
||||
p.Elements = append(p.Elements, &ParseElement{Clause: cmd})
|
||||
p.mergeFlags(cmd.flagGroup)
|
||||
p.mergeArgs(cmd.argGroup)
|
||||
p.SelectedCommand = cmd
|
||||
}
|
||||
|
||||
// Expand arguments from a file. Lines starting with # will be treated as comments.
|
||||
func ExpandArgsFromFile(filename string) (out []string, err error) {
|
||||
if filename == "" {
|
||||
return nil, fmt.Errorf("expected @ file to expand arguments from")
|
||||
}
|
||||
r, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open arguments file %q: %s", filename, err)
|
||||
}
|
||||
defer r.Close()
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "#") || strings.TrimSpace(line) == "" {
|
||||
continue
|
||||
}
|
||||
out = append(out, line)
|
||||
}
|
||||
err = scanner.Err()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read arguments from %q: %s", filename, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parse(context *ParseContext, app *Application) (err error) {
|
||||
context.mergeFlags(app.flagGroup)
|
||||
context.mergeArgs(app.argGroup)
|
||||
|
||||
cmds := app.cmdGroup
|
||||
ignoreDefault := context.ignoreDefault
|
||||
|
||||
loop:
|
||||
for !context.EOL() && !context.Error() {
|
||||
token := context.Peek()
|
||||
|
||||
switch token.Type {
|
||||
case TokenLong, TokenShort:
|
||||
if flag, err := context.flags.parse(context); err != nil {
|
||||
if !ignoreDefault {
|
||||
if cmd := cmds.defaultSubcommand(); cmd != nil {
|
||||
cmd.completionAlts = cmds.cmdNames()
|
||||
context.matchedCmd(cmd)
|
||||
cmds = cmd.cmdGroup
|
||||
break
|
||||
}
|
||||
}
|
||||
return err
|
||||
} else if flag == HelpFlag {
|
||||
ignoreDefault = true
|
||||
}
|
||||
|
||||
case TokenArg:
|
||||
if cmds.have() {
|
||||
selectedDefault := false
|
||||
cmd, ok := cmds.commands[token.String()]
|
||||
if !ok {
|
||||
if !ignoreDefault {
|
||||
if cmd = cmds.defaultSubcommand(); cmd != nil {
|
||||
cmd.completionAlts = cmds.cmdNames()
|
||||
selectedDefault = true
|
||||
}
|
||||
}
|
||||
if cmd == nil {
|
||||
return fmt.Errorf("expected command but got %q", token)
|
||||
}
|
||||
}
|
||||
if cmd == HelpCommand {
|
||||
ignoreDefault = true
|
||||
}
|
||||
cmd.completionAlts = nil
|
||||
context.matchedCmd(cmd)
|
||||
cmds = cmd.cmdGroup
|
||||
if !selectedDefault {
|
||||
context.Next()
|
||||
}
|
||||
} else if context.arguments.have() {
|
||||
if app.noInterspersed {
|
||||
// no more flags
|
||||
context.argsOnly = true
|
||||
}
|
||||
arg := context.nextArg()
|
||||
if arg == nil {
|
||||
break loop
|
||||
}
|
||||
context.matchedArg(arg, token.String())
|
||||
context.Next()
|
||||
} else {
|
||||
break loop
|
||||
}
|
||||
|
||||
case TokenEOL:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
// Move to innermost default command.
|
||||
for !ignoreDefault {
|
||||
if cmd := cmds.defaultSubcommand(); cmd != nil {
|
||||
cmd.completionAlts = cmds.cmdNames()
|
||||
context.matchedCmd(cmd)
|
||||
cmds = cmd.cmdGroup
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if context.Error() {
|
||||
return fmt.Errorf("%s", context.Peek().Value)
|
||||
}
|
||||
|
||||
if !context.EOL() {
|
||||
return fmt.Errorf("unexpected %s", context.Peek())
|
||||
}
|
||||
|
||||
// Set defaults for all remaining args.
|
||||
for arg := context.nextArg(); arg != nil && !arg.consumesRemainder(); arg = context.nextArg() {
|
||||
for _, defaultValue := range arg.defaultValues {
|
||||
if err := arg.value.Set(defaultValue); err != nil {
|
||||
return fmt.Errorf("invalid default value '%s' for argument '%s'", defaultValue, arg.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
package kingpin
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/alecthomas/units"
|
||||
)
|
||||
|
||||
type Settings interface {
|
||||
SetValue(value Value)
|
||||
}
|
||||
|
||||
type parserMixin struct {
|
||||
value Value
|
||||
required bool
|
||||
}
|
||||
|
||||
func (p *parserMixin) SetText(text Text) {
|
||||
p.value = &wrapText{text}
|
||||
}
|
||||
|
||||
func (p *parserMixin) SetValue(value Value) {
|
||||
p.value = value
|
||||
}
|
||||
|
||||
// StringMap provides key=value parsing into a map.
|
||||
func (p *parserMixin) StringMap() (target *map[string]string) {
|
||||
target = &(map[string]string{})
|
||||
p.StringMapVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// Duration sets the parser to a time.Duration parser.
|
||||
func (p *parserMixin) Duration() (target *time.Duration) {
|
||||
target = new(time.Duration)
|
||||
p.DurationVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// Bytes parses numeric byte units. eg. 1.5KB
|
||||
func (p *parserMixin) Bytes() (target *units.Base2Bytes) {
|
||||
target = new(units.Base2Bytes)
|
||||
p.BytesVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// IP sets the parser to a net.IP parser.
|
||||
func (p *parserMixin) IP() (target *net.IP) {
|
||||
target = new(net.IP)
|
||||
p.IPVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// TCP (host:port) address.
|
||||
func (p *parserMixin) TCP() (target **net.TCPAddr) {
|
||||
target = new(*net.TCPAddr)
|
||||
p.TCPVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// TCPVar (host:port) address.
|
||||
func (p *parserMixin) TCPVar(target **net.TCPAddr) {
|
||||
p.SetValue(newTCPAddrValue(target))
|
||||
}
|
||||
|
||||
// ExistingFile sets the parser to one that requires and returns an existing file.
|
||||
func (p *parserMixin) ExistingFile() (target *string) {
|
||||
target = new(string)
|
||||
p.ExistingFileVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// ExistingDir sets the parser to one that requires and returns an existing directory.
|
||||
func (p *parserMixin) ExistingDir() (target *string) {
|
||||
target = new(string)
|
||||
p.ExistingDirVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// ExistingFileOrDir sets the parser to one that requires and returns an existing file OR directory.
|
||||
func (p *parserMixin) ExistingFileOrDir() (target *string) {
|
||||
target = new(string)
|
||||
p.ExistingFileOrDirVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// File returns an os.File against an existing file.
|
||||
func (p *parserMixin) File() (target **os.File) {
|
||||
target = new(*os.File)
|
||||
p.FileVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// File attempts to open a File with os.OpenFile(flag, perm).
|
||||
func (p *parserMixin) OpenFile(flag int, perm os.FileMode) (target **os.File) {
|
||||
target = new(*os.File)
|
||||
p.OpenFileVar(target, flag, perm)
|
||||
return
|
||||
}
|
||||
|
||||
// URL provides a valid, parsed url.URL.
|
||||
func (p *parserMixin) URL() (target **url.URL) {
|
||||
target = new(*url.URL)
|
||||
p.URLVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// StringMap provides key=value parsing into a map.
|
||||
func (p *parserMixin) StringMapVar(target *map[string]string) {
|
||||
p.SetValue(newStringMapValue(target))
|
||||
}
|
||||
|
||||
// Float sets the parser to a float64 parser.
|
||||
func (p *parserMixin) Float() (target *float64) {
|
||||
return p.Float64()
|
||||
}
|
||||
|
||||
// Float sets the parser to a float64 parser.
|
||||
func (p *parserMixin) FloatVar(target *float64) {
|
||||
p.Float64Var(target)
|
||||
}
|
||||
|
||||
// Duration sets the parser to a time.Duration parser.
|
||||
func (p *parserMixin) DurationVar(target *time.Duration) {
|
||||
p.SetValue(newDurationValue(target))
|
||||
}
|
||||
|
||||
// BytesVar parses numeric byte units. eg. 1.5KB
|
||||
func (p *parserMixin) BytesVar(target *units.Base2Bytes) {
|
||||
p.SetValue(newBytesValue(target))
|
||||
}
|
||||
|
||||
// IP sets the parser to a net.IP parser.
|
||||
func (p *parserMixin) IPVar(target *net.IP) {
|
||||
p.SetValue(newIPValue(target))
|
||||
}
|
||||
|
||||
// ExistingFile sets the parser to one that requires and returns an existing file.
|
||||
func (p *parserMixin) ExistingFileVar(target *string) {
|
||||
p.SetValue(newExistingFileValue(target))
|
||||
}
|
||||
|
||||
// ExistingDir sets the parser to one that requires and returns an existing directory.
|
||||
func (p *parserMixin) ExistingDirVar(target *string) {
|
||||
p.SetValue(newExistingDirValue(target))
|
||||
}
|
||||
|
||||
// ExistingDir sets the parser to one that requires and returns an existing directory.
|
||||
func (p *parserMixin) ExistingFileOrDirVar(target *string) {
|
||||
p.SetValue(newExistingFileOrDirValue(target))
|
||||
}
|
||||
|
||||
// FileVar opens an existing file.
|
||||
func (p *parserMixin) FileVar(target **os.File) {
|
||||
p.SetValue(newFileValue(target, os.O_RDONLY, 0))
|
||||
}
|
||||
|
||||
// OpenFileVar calls os.OpenFile(flag, perm)
|
||||
func (p *parserMixin) OpenFileVar(target **os.File, flag int, perm os.FileMode) {
|
||||
p.SetValue(newFileValue(target, flag, perm))
|
||||
}
|
||||
|
||||
// URL provides a valid, parsed url.URL.
|
||||
func (p *parserMixin) URLVar(target **url.URL) {
|
||||
p.SetValue(newURLValue(target))
|
||||
}
|
||||
|
||||
// URLList provides a parsed list of url.URL values.
|
||||
func (p *parserMixin) URLList() (target *[]*url.URL) {
|
||||
target = new([]*url.URL)
|
||||
p.URLListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// URLListVar provides a parsed list of url.URL values.
|
||||
func (p *parserMixin) URLListVar(target *[]*url.URL) {
|
||||
p.SetValue(newURLListValue(target))
|
||||
}
|
||||
|
||||
// Enum allows a value from a set of options.
|
||||
func (p *parserMixin) Enum(options ...string) (target *string) {
|
||||
target = new(string)
|
||||
p.EnumVar(target, options...)
|
||||
return
|
||||
}
|
||||
|
||||
// EnumVar allows a value from a set of options.
|
||||
func (p *parserMixin) EnumVar(target *string, options ...string) {
|
||||
p.SetValue(newEnumFlag(target, options...))
|
||||
}
|
||||
|
||||
// Enums allows a set of values from a set of options.
|
||||
func (p *parserMixin) Enums(options ...string) (target *[]string) {
|
||||
target = new([]string)
|
||||
p.EnumsVar(target, options...)
|
||||
return
|
||||
}
|
||||
|
||||
// EnumVar allows a value from a set of options.
|
||||
func (p *parserMixin) EnumsVar(target *[]string, options ...string) {
|
||||
p.SetValue(newEnumsFlag(target, options...))
|
||||
}
|
||||
|
||||
// A Counter increments a number each time it is encountered.
|
||||
func (p *parserMixin) Counter() (target *int) {
|
||||
target = new(int)
|
||||
p.CounterVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) CounterVar(target *int) {
|
||||
p.SetValue(newCounterValue(target))
|
||||
}
|
|
@ -0,0 +1,262 @@
|
|||
package kingpin
|
||||
|
||||
// Default usage template.
|
||||
var DefaultUsageTemplate = `{{define "FormatCommand" -}}
|
||||
{{if .FlagSummary}} {{.FlagSummary}}{{end -}}
|
||||
{{range .Args}}{{if not .Hidden}} {{if not .Required}}[{{end}}{{if .PlaceHolder}}{{.PlaceHolder}}{{else}}<{{.Name}}>{{end}}{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}{{end -}}
|
||||
{{end -}}
|
||||
|
||||
{{define "FormatCommands" -}}
|
||||
{{range .FlattenedCommands -}}
|
||||
{{if not .Hidden -}}
|
||||
{{.FullCommand}}{{if .Default}}*{{end}}{{template "FormatCommand" .}}
|
||||
{{.Help|Wrap 4}}
|
||||
{{end -}}
|
||||
{{end -}}
|
||||
{{end -}}
|
||||
|
||||
{{define "FormatUsage" -}}
|
||||
{{template "FormatCommand" .}}{{if .Commands}} <command> [<args> ...]{{end}}
|
||||
{{if .Help}}
|
||||
{{.Help|Wrap 0 -}}
|
||||
{{end -}}
|
||||
|
||||
{{end -}}
|
||||
|
||||
{{if .Context.SelectedCommand -}}
|
||||
usage: {{.App.Name}} {{.Context.SelectedCommand}}{{template "FormatUsage" .Context.SelectedCommand}}
|
||||
{{ else -}}
|
||||
usage: {{.App.Name}}{{template "FormatUsage" .App}}
|
||||
{{end}}
|
||||
{{if .Context.Flags -}}
|
||||
Flags:
|
||||
{{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}}
|
||||
{{end -}}
|
||||
{{if .Context.Args -}}
|
||||
Args:
|
||||
{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}}
|
||||
{{end -}}
|
||||
{{if .Context.SelectedCommand -}}
|
||||
{{if len .Context.SelectedCommand.Commands -}}
|
||||
Subcommands:
|
||||
{{template "FormatCommands" .Context.SelectedCommand}}
|
||||
{{end -}}
|
||||
{{else if .App.Commands -}}
|
||||
Commands:
|
||||
{{template "FormatCommands" .App}}
|
||||
{{end -}}
|
||||
`
|
||||
|
||||
// Usage template where command's optional flags are listed separately
|
||||
var SeparateOptionalFlagsUsageTemplate = `{{define "FormatCommand" -}}
|
||||
{{if .FlagSummary}} {{.FlagSummary}}{{end -}}
|
||||
{{range .Args}}{{if not .Hidden}} {{if not .Required}}[{{end}}{{if .PlaceHolder}}{{.PlaceHolder}}{{else}}<{{.Name}}>{{end}}{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}{{end -}}
|
||||
{{end -}}
|
||||
|
||||
{{define "FormatCommands" -}}
|
||||
{{range .FlattenedCommands -}}
|
||||
{{if not .Hidden -}}
|
||||
{{.FullCommand}}{{if .Default}}*{{end}}{{template "FormatCommand" .}}
|
||||
{{.Help|Wrap 4}}
|
||||
{{end -}}
|
||||
{{end -}}
|
||||
{{end -}}
|
||||
|
||||
{{define "FormatUsage" -}}
|
||||
{{template "FormatCommand" .}}{{if .Commands}} <command> [<args> ...]{{end}}
|
||||
{{if .Help}}
|
||||
{{.Help|Wrap 0 -}}
|
||||
{{end -}}
|
||||
|
||||
{{end -}}
|
||||
{{if .Context.SelectedCommand -}}
|
||||
usage: {{.App.Name}} {{.Context.SelectedCommand}}{{template "FormatUsage" .Context.SelectedCommand}}
|
||||
{{else -}}
|
||||
usage: {{.App.Name}}{{template "FormatUsage" .App}}
|
||||
{{end -}}
|
||||
|
||||
{{if .Context.Flags|RequiredFlags -}}
|
||||
Required flags:
|
||||
{{.Context.Flags|RequiredFlags|FlagsToTwoColumns|FormatTwoColumns}}
|
||||
{{end -}}
|
||||
{{if .Context.Flags|OptionalFlags -}}
|
||||
Optional flags:
|
||||
{{.Context.Flags|OptionalFlags|FlagsToTwoColumns|FormatTwoColumns}}
|
||||
{{end -}}
|
||||
{{if .Context.Args -}}
|
||||
Args:
|
||||
{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}}
|
||||
{{end -}}
|
||||
{{if .Context.SelectedCommand -}}
|
||||
Subcommands:
|
||||
{{if .Context.SelectedCommand.Commands -}}
|
||||
{{template "FormatCommands" .Context.SelectedCommand}}
|
||||
{{end -}}
|
||||
{{else if .App.Commands -}}
|
||||
Commands:
|
||||
{{template "FormatCommands" .App}}
|
||||
{{end -}}
|
||||
`
|
||||
|
||||
// Usage template with compactly formatted commands.
|
||||
var CompactUsageTemplate = `{{define "FormatCommand" -}}
|
||||
{{if .FlagSummary}} {{.FlagSummary}}{{end -}}
|
||||
{{range .Args}}{{if not .Hidden}} {{if not .Required}}[{{end}}{{if .PlaceHolder}}{{.PlaceHolder}}{{else}}<{{.Name}}>{{end}}{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}{{end -}}
|
||||
{{end -}}
|
||||
|
||||
{{define "FormatCommandList" -}}
|
||||
{{range . -}}
|
||||
{{if not .Hidden -}}
|
||||
{{.Depth|Indent}}{{.Name}}{{if .Default}}*{{end}}{{template "FormatCommand" .}}
|
||||
{{end -}}
|
||||
{{template "FormatCommandList" .Commands -}}
|
||||
{{end -}}
|
||||
{{end -}}
|
||||
|
||||
{{define "FormatUsage" -}}
|
||||
{{template "FormatCommand" .}}{{if .Commands}} <command> [<args> ...]{{end}}
|
||||
{{if .Help}}
|
||||
{{.Help|Wrap 0 -}}
|
||||
{{end -}}
|
||||
|
||||
{{end -}}
|
||||
|
||||
{{if .Context.SelectedCommand -}}
|
||||
usage: {{.App.Name}} {{.Context.SelectedCommand}}{{template "FormatUsage" .Context.SelectedCommand}}
|
||||
{{else -}}
|
||||
usage: {{.App.Name}}{{template "FormatUsage" .App}}
|
||||
{{end -}}
|
||||
{{if .Context.Flags -}}
|
||||
Flags:
|
||||
{{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}}
|
||||
{{end -}}
|
||||
{{if .Context.Args -}}
|
||||
Args:
|
||||
{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}}
|
||||
{{end -}}
|
||||
{{if .Context.SelectedCommand -}}
|
||||
{{if .Context.SelectedCommand.Commands -}}
|
||||
Commands:
|
||||
{{.Context.SelectedCommand}}
|
||||
{{template "FormatCommandList" .Context.SelectedCommand.Commands}}
|
||||
{{end -}}
|
||||
{{else if .App.Commands -}}
|
||||
Commands:
|
||||
{{template "FormatCommandList" .App.Commands}}
|
||||
{{end -}}
|
||||
`
|
||||
|
||||
var ManPageTemplate = `{{define "FormatFlags" -}}
|
||||
{{range .Flags -}}
|
||||
{{if not .Hidden -}}
|
||||
.TP
|
||||
\fB{{if .Short}}-{{.Short|Char}}, {{end}}--{{.Name}}{{if not .IsBoolFlag}}={{.FormatPlaceHolder}}{{end -}}\fR
|
||||
{{.Help}}
|
||||
{{end -}}
|
||||
{{end -}}
|
||||
{{end -}}
|
||||
|
||||
{{define "FormatCommand" -}}
|
||||
{{if .FlagSummary}} {{.FlagSummary}}{{end -}}
|
||||
{{range .Args}}{{if not .Hidden}} {{if not .Required}}[{{end}}{{if .PlaceHolder}}{{.PlaceHolder}}{{else}}<{{.Name}}>{{end}}{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}{{end -}}
|
||||
{{end -}}
|
||||
|
||||
{{define "FormatCommands" -}}
|
||||
{{range .FlattenedCommands -}}
|
||||
{{if not .Hidden -}}
|
||||
.SS
|
||||
\fB{{.FullCommand}}{{template "FormatCommand" . -}}\fR
|
||||
.PP
|
||||
{{.Help}}
|
||||
{{template "FormatFlags" . -}}
|
||||
{{end -}}
|
||||
{{end -}}
|
||||
{{end -}}
|
||||
|
||||
{{define "FormatUsage" -}}
|
||||
{{template "FormatCommand" .}}{{if .Commands}} <command> [<args> ...]{{end -}}\fR
|
||||
{{end -}}
|
||||
|
||||
.TH {{.App.Name}} 1 {{.App.Version}} "{{.App.Author}}"
|
||||
.SH "NAME"
|
||||
{{.App.Name}}
|
||||
.SH "SYNOPSIS"
|
||||
.TP
|
||||
\fB{{.App.Name}}{{template "FormatUsage" .App}}
|
||||
.SH "DESCRIPTION"
|
||||
{{.App.Help}}
|
||||
.SH "OPTIONS"
|
||||
{{template "FormatFlags" .App -}}
|
||||
{{if .App.Commands -}}
|
||||
.SH "COMMANDS"
|
||||
{{template "FormatCommands" .App -}}
|
||||
{{end -}}
|
||||
`
|
||||
|
||||
// Default usage template.
|
||||
var LongHelpTemplate = `{{define "FormatCommand" -}}
|
||||
{{if .FlagSummary}} {{.FlagSummary}}{{end -}}
|
||||
{{range .Args}}{{if not .Hidden}} {{if not .Required}}[{{end}}{{if .PlaceHolder}}{{.PlaceHolder}}{{else}}<{{.Name}}>{{end}}{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}{{end -}}
|
||||
{{end -}}
|
||||
|
||||
{{define "FormatCommands" -}}
|
||||
{{range .FlattenedCommands -}}
|
||||
{{if not .Hidden -}}
|
||||
{{.FullCommand}}{{template "FormatCommand" .}}
|
||||
{{.Help|Wrap 4}}
|
||||
{{with .Flags|FlagsToTwoColumns}}{{FormatTwoColumnsWithIndent . 4 2}}{{end}}
|
||||
{{end -}}
|
||||
{{end -}}
|
||||
{{end -}}
|
||||
|
||||
{{define "FormatUsage" -}}
|
||||
{{template "FormatCommand" .}}{{if .Commands}} <command> [<args> ...]{{end}}
|
||||
{{if .Help}}
|
||||
{{.Help|Wrap 0 -}}
|
||||
{{end -}}
|
||||
|
||||
{{end -}}
|
||||
|
||||
usage: {{.App.Name}}{{template "FormatUsage" .App}}
|
||||
{{if .Context.Flags -}}
|
||||
Flags:
|
||||
{{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}}
|
||||
{{end -}}
|
||||
{{if .Context.Args -}}
|
||||
Args:
|
||||
{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}}
|
||||
{{end -}}
|
||||
{{if .App.Commands -}}
|
||||
Commands:
|
||||
{{template "FormatCommands" .App}}
|
||||
{{end -}}
|
||||
`
|
||||
|
||||
var BashCompletionTemplate = `
|
||||
_{{.App.Name}}_bash_autocomplete() {
|
||||
local cur prev opts base
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
opts=$( ${COMP_WORDS[0]} --completion-bash "${COMP_WORDS[@]:1:$COMP_CWORD}" )
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
}
|
||||
complete -F _{{.App.Name}}_bash_autocomplete -o default {{.App.Name}}
|
||||
|
||||
`
|
||||
|
||||
var ZshCompletionTemplate = `#compdef {{.App.Name}}
|
||||
|
||||
_{{.App.Name}}() {
|
||||
local matches=($(${words[1]} --completion-bash "${(@)words[2,$CURRENT]}"))
|
||||
compadd -a matches
|
||||
|
||||
if [[ $compstate[nmatches] -eq 0 && $words[$CURRENT] != -* ]]; then
|
||||
_files
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ "$(basename -- ${(%):-%x})" != "_{{.App.Name}}" ]]; then
|
||||
compdef _{{.App.Name}} {{.App.Name}}
|
||||
fi
|
||||
`
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue