Compare commits
14 Commits
feat/input
...
8f528f27f3
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f528f27f3 | |||
| 0247f58668 | |||
| d9d6bf6cd6 | |||
| 3cd3d89b60 | |||
| 2bdc945ec5 | |||
| 8234a0cefa | |||
| f1566b8e4a | |||
| 71c93f2e73 | |||
| fdedd70471 | |||
| 0fe45a3c9c | |||
| 3d81336c9b | |||
| d0c8ef76fc | |||
| 5a99160f65 | |||
| c2f3756cef |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -284,3 +284,6 @@ local.properties
|
|||||||
|
|
||||||
./rc-*
|
./rc-*
|
||||||
rc-simulator
|
rc-simulator
|
||||||
|
rc-simulator.*
|
||||||
|
|
||||||
|
build/
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM --platform=$BUILDPLATFORM golang:alpine AS builder
|
FROM --platform=$BUILDPLATFORM golang:1.17-alpine AS builder
|
||||||
|
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
ARG BUILDPLATFORM
|
ARG BUILDPLATFORM
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Usage of ./rc-simulator:
|
|||||||
-debug
|
-debug
|
||||||
Debug logs
|
Debug logs
|
||||||
-events-topic-frame string
|
-events-topic-frame string
|
||||||
Mqtt topic to events gateway frames, use MQTT_TOPIC_FRAME if args not set
|
Mqtt topic to events gateway frames, use MQTT_TOPIC_CAMERA if args not set
|
||||||
-events-topic-steering string
|
-events-topic-steering string
|
||||||
Mqtt topic to events gateway steering, use MQTT_TOPIC_STEERING if args not set
|
Mqtt topic to events gateway steering, use MQTT_TOPIC_STEERING if args not set
|
||||||
-events-topic-throttle string
|
-events-topic-throttle string
|
||||||
|
|||||||
50
build-docker.sh
Executable file
50
build-docker.sh
Executable file
@@ -0,0 +1,50 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
IMAGE_NAME=robocar-simulator
|
||||||
|
TAG=$(git describe)
|
||||||
|
FULL_IMAGE_NAME=docker.io/cyrilix/${IMAGE_NAME}:${TAG}
|
||||||
|
BINARY=rc-simulator
|
||||||
|
|
||||||
|
GOTAGS="-tags netgo"
|
||||||
|
|
||||||
|
image_build(){
|
||||||
|
local platform=$1
|
||||||
|
|
||||||
|
|
||||||
|
GOOS=$(echo $platform | cut -f1 -d/) && \
|
||||||
|
GOARCH=$(echo $platform | cut -f2 -d/) && \
|
||||||
|
GOARM=$(echo $platform | cut -f3 -d/ | sed "s/v//" )
|
||||||
|
VARIANT="--variant $(echo $platform | cut -f3 -d/ )"
|
||||||
|
if [[ -z "$GOARM" ]] ;
|
||||||
|
then
|
||||||
|
VARIANT=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
local binary_suffix="$GOARCH$(echo $platform | cut -f3 -d/ )"
|
||||||
|
|
||||||
|
local containerName="$IMAGE_NAME-$GOARCH$GOARM"
|
||||||
|
|
||||||
|
|
||||||
|
printf "\n\nBuild go binary %s\n\n" "${BINARY}.${binary_suffix}"
|
||||||
|
mkdir -p build
|
||||||
|
CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} GOARM=${GOARM} go build -mod vendor -a ${GOTAGS} -o "build/${BINARY}.${binary_suffix}" ./cmd/${BINARY}/
|
||||||
|
|
||||||
|
buildah --os "$GOOS" --arch "$GOARCH" $VARIANT --name "$containerName" from gcr.io/distroless/static
|
||||||
|
buildah config --user 1234 "$containerName"
|
||||||
|
buildah copy "$containerName" "build/${BINARY}.${binary_suffix}" /go/bin/$BINARY
|
||||||
|
buildah config --entrypoint '["/go/bin/'$BINARY'"]' "${containerName}"
|
||||||
|
|
||||||
|
buildah commit --rm --manifest $IMAGE_NAME "${containerName}" "${containerName}"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildah rmi localhost/$IMAGE_NAME
|
||||||
|
buildah manifest rm localhost/${IMAGE_NAME}
|
||||||
|
|
||||||
|
image_build linux/amd64
|
||||||
|
image_build linux/arm64
|
||||||
|
image_build linux/arm/v7
|
||||||
|
|
||||||
|
|
||||||
|
# push image
|
||||||
|
printf "\n\nPush manifest to %s\n\n" ${FULL_IMAGE_NAME}
|
||||||
|
buildah manifest push --rm -f v2s2 "localhost/$IMAGE_NAME" "docker://$FULL_IMAGE_NAME" --all
|
||||||
@@ -2,14 +2,19 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
"github.com/cyrilix/robocar-base/cli"
|
"github.com/cyrilix/robocar-base/cli"
|
||||||
events2 "github.com/cyrilix/robocar-protobuf/go/events"
|
events2 "github.com/cyrilix/robocar-protobuf/go/events"
|
||||||
"github.com/cyrilix/robocar-simulator/pkg/events"
|
"github.com/cyrilix/robocar-simulator/pkg/events"
|
||||||
"github.com/cyrilix/robocar-simulator/pkg/gateway"
|
"github.com/cyrilix/robocar-simulator/pkg/gateway"
|
||||||
|
"github.com/cyrilix/robocar-simulator/pkg/simulator"
|
||||||
mqtt "github.com/eclipse/paho.mqtt.golang"
|
mqtt "github.com/eclipse/paho.mqtt.golang"
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
log "github.com/sirupsen/logrus"
|
"go.uber.org/zap"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DefaultClientId = "robocar-simulator"
|
const DefaultClientId = "robocar-simulator"
|
||||||
@@ -18,37 +23,121 @@ func main() {
|
|||||||
var mqttBroker, username, password, clientId, topicFrame, topicSteering, topicThrottle string
|
var mqttBroker, username, password, clientId, topicFrame, topicSteering, topicThrottle string
|
||||||
var topicCtrlSteering, topicCtrlThrottle string
|
var topicCtrlSteering, topicCtrlThrottle string
|
||||||
var address string
|
var address string
|
||||||
var debug bool
|
|
||||||
|
|
||||||
mqttQos := cli.InitIntFlag("MQTT_QOS", 0)
|
mqttQos := cli.InitIntFlag("MQTT_QOS", 0)
|
||||||
_, mqttRetain := os.LookupEnv("MQTT_RETAIN")
|
_, mqttRetain := os.LookupEnv("MQTT_RETAIN")
|
||||||
|
|
||||||
cli.InitMqttFlags(DefaultClientId, &mqttBroker, &username, &password, &clientId, &mqttQos, &mqttRetain)
|
cli.InitMqttFlags(DefaultClientId, &mqttBroker, &username, &password, &clientId, &mqttQos, &mqttRetain)
|
||||||
|
|
||||||
flag.StringVar(&topicFrame, "events-topic-frame", os.Getenv("MQTT_TOPIC_FRAME"), "Mqtt topic to events gateway frames, use MQTT_TOPIC_FRAME if args not set")
|
flag.StringVar(&topicFrame, "events-topic-camera", os.Getenv("MQTT_TOPIC_CAMERA"), "Mqtt topic to events gateway frames, use MQTT_TOPIC_CAMERA if args not set")
|
||||||
flag.StringVar(&topicFrame, "events-topic-steering", os.Getenv("MQTT_TOPIC_STEERING"), "Mqtt topic to events gateway steering, use MQTT_TOPIC_STEERING if args not set")
|
flag.StringVar(&topicSteering, "events-topic-steering", os.Getenv("MQTT_TOPIC_STEERING"), "Mqtt topic to events gateway steering, use MQTT_TOPIC_STEERING if args not set")
|
||||||
flag.StringVar(&topicFrame, "events-topic-throttle", os.Getenv("MQTT_TOPIC_THROTTLE"), "Mqtt topic to events gateway throttle, use MQTT_TOPIC_THROTTLE if args not set")
|
flag.StringVar(&topicThrottle, "events-topic-throttle", os.Getenv("MQTT_TOPIC_THROTTLE"), "Mqtt topic to events gateway throttle, use MQTT_TOPIC_THROTTLE if args not set")
|
||||||
flag.StringVar(&topicFrame, "topic-steering-ctrl", os.Getenv("MQTT_TOPIC_STEERING_CTRL"), "Mqtt topic to send steering instructions, use MQTT_TOPIC_STEERING_CTRL if args not set")
|
flag.StringVar(&topicCtrlSteering, "topic-steering-ctrl", os.Getenv("MQTT_TOPIC_STEERING_CTRL"), "Mqtt topic to send steering instructions, use MQTT_TOPIC_STEERING_CTRL if args not set")
|
||||||
flag.StringVar(&topicFrame, "topic-throttle-ctrl", os.Getenv("MQTT_TOPIC_THROTTLE_CTRL"), "Mqtt topic to send throttle instructions, use MQTT_TOPIC_THROTTLE_CTRL if args not set")
|
flag.StringVar(&topicCtrlThrottle, "topic-throttle-ctrl", os.Getenv("MQTT_TOPIC_THROTTLE_CTRL"), "Mqtt topic to send throttle instructions, use MQTT_TOPIC_THROTTLE_CTRL if args not set")
|
||||||
flag.StringVar(&address, "simulator-address", "127.0.0.1:9091", "Simulator address")
|
flag.StringVar(&address, "simulator-address", "127.0.0.1:9091", "Simulator address")
|
||||||
flag.BoolVar(&debug, "debug", false, "Debug logs")
|
|
||||||
|
var carName, carStyle, carColor string
|
||||||
|
var carFontSize int
|
||||||
|
carStyles := []string{
|
||||||
|
string(simulator.CarConfigBodyStyleDonkey),
|
||||||
|
string(simulator.CarConfigBodyStyleBare),
|
||||||
|
string(simulator.CarConfigBodyStyleCar01),
|
||||||
|
}
|
||||||
|
flag.StringVar(&carName, "car-name", "simulator-gateway", "Car name to display")
|
||||||
|
flag.StringVar(&carStyle, "car-style", string(simulator.CarConfigBodyStyleDonkey), fmt.Sprintf("Car style, only %s", strings.Join(carStyles, ",")))
|
||||||
|
flag.StringVar(&carColor, "car-color", "0,0,0", "Color car as rgb value")
|
||||||
|
flag.IntVar(&carFontSize, "car-font-size", 0, "Car font size")
|
||||||
|
|
||||||
|
var racerName, racerBio, racerCountry, racerGuid string
|
||||||
|
flag.StringVar(&racerName, "racer-name", "", "")
|
||||||
|
flag.StringVar(&racerBio, "racer-bio", "", "")
|
||||||
|
flag.StringVar(&racerCountry, "racer-country", "", "")
|
||||||
|
flag.StringVar(&racerGuid, "racer-guid", "", "")
|
||||||
|
|
||||||
|
var cameraFov, cameraImgW, cameraImgH, cameraImgD int
|
||||||
|
var cameraFishEyeX, cameraFishEyeY float64
|
||||||
|
var cameraOffsetX, cameraOffsetY, cameraOffsetZ, cameraRotX, cameraRotY, cameraRotZ float64
|
||||||
|
var cameraImgEnc string
|
||||||
|
flag.IntVar(&cameraFov, "camera-fov", 90, "")
|
||||||
|
flag.Float64Var(&cameraFishEyeX, "camera-fish-eye-x", 0.4, "")
|
||||||
|
flag.Float64Var(&cameraFishEyeY, "camera-fish-eye-y", 0.7, "")
|
||||||
|
flag.IntVar(&cameraImgW, "camera-img-w", 160, "image width")
|
||||||
|
flag.IntVar(&cameraImgH, "camera-img-h", 128, "image height")
|
||||||
|
flag.IntVar(&cameraImgD, "camera-img-d", 3, "Image depth")
|
||||||
|
flag.StringVar(&cameraImgEnc, "camera-img-enc", string(simulator.CameraImageEncJpeg), "")
|
||||||
|
flag.Float64Var(&cameraOffsetX, "camera-offset-x", 0, "moves camera left/right")
|
||||||
|
flag.Float64Var(&cameraOffsetY, "camera-offset-y", 1.120395, "moves camera up/down")
|
||||||
|
flag.Float64Var(&cameraOffsetZ, "camera-offset-z", 0.5528488, "moves camera forward/back")
|
||||||
|
flag.Float64Var(&cameraRotX, "camera-rot-x", 15.0, "rotate the camera around x-axis")
|
||||||
|
flag.Float64Var(&cameraRotY, "camera-rot-y", 0.0, "rotate the camera around y-axis")
|
||||||
|
flag.Float64Var(&cameraRotZ, "camera-rot-z", 0.0, "rotate the camera around z-axis")
|
||||||
|
|
||||||
|
logLevel := zap.LevelFlag("log", zap.InfoLevel, "log level")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if len(os.Args) <= 1 {
|
if len(os.Args) <= 1 {
|
||||||
flag.PrintDefaults()
|
flag.PrintDefaults()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if debug {
|
|
||||||
log.SetLevel(log.DebugLevel)
|
config := zap.NewDevelopmentConfig()
|
||||||
|
config.Level = zap.NewAtomicLevelAt(*logLevel)
|
||||||
|
lgr, err := config.Build()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to init logger: %v", err)
|
||||||
}
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := lgr.Sync(); err != nil {
|
||||||
|
log.Printf("unable to Sync logger: %v\n", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
zap.ReplaceGlobals(lgr)
|
||||||
|
|
||||||
client, err := cli.Connect(mqttBroker, username, password, clientId)
|
client, err := cli.Connect(mqttBroker, username, password, clientId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("unable to connect to events broker: %v", err)
|
zap.S().Fatalf("unable to connect to events broker: %v", err)
|
||||||
}
|
}
|
||||||
defer client.Disconnect(10)
|
defer client.Disconnect(10)
|
||||||
|
|
||||||
gtw := gateway.New(address)
|
bodyColors := strings.Split(carColor, ",")
|
||||||
|
carConfig := simulator.CarConfigMsg{
|
||||||
|
MsgType: simulator.MsgTypeCarConfig,
|
||||||
|
BodyStyle: simulator.CarStyle(carStyle),
|
||||||
|
BodyR: bodyColors[0],
|
||||||
|
BodyG: bodyColors[1],
|
||||||
|
BodyB: bodyColors[2],
|
||||||
|
CarName: carName,
|
||||||
|
FontSize: strconv.Itoa(carFontSize),
|
||||||
|
}
|
||||||
|
racer := simulator.RacerBioMsg{
|
||||||
|
MsgType: simulator.MsgTypeRacerInfo,
|
||||||
|
RacerName: racerName,
|
||||||
|
CarName: carName,
|
||||||
|
Bio: racerBio,
|
||||||
|
Country: racerCountry,
|
||||||
|
Guid: racerGuid,
|
||||||
|
}
|
||||||
|
camera := simulator.CamConfigMsg{
|
||||||
|
MsgType: simulator.MsgTypeCameraConfig,
|
||||||
|
Fov: strconv.Itoa(cameraFov),
|
||||||
|
FishEyeX: fmt.Sprintf("%.2f", cameraFishEyeX),
|
||||||
|
FishEyeY: fmt.Sprintf("%.2f", cameraFishEyeY),
|
||||||
|
ImgW: strconv.Itoa(cameraImgW),
|
||||||
|
ImgH: strconv.Itoa(cameraImgH),
|
||||||
|
ImgD: strconv.Itoa(cameraImgD),
|
||||||
|
ImgEnc: simulator.CameraImageEnc(cameraImgEnc),
|
||||||
|
OffsetX: fmt.Sprintf("%.2f", cameraOffsetX),
|
||||||
|
OffsetY: fmt.Sprintf("%.2f", cameraOffsetY),
|
||||||
|
OffsetZ: fmt.Sprintf("%.2f", cameraOffsetZ),
|
||||||
|
RotX: fmt.Sprintf("%.2f", cameraRotX),
|
||||||
|
RotY: fmt.Sprintf("%.2f", cameraRotY),
|
||||||
|
RotZ: fmt.Sprintf("%.2f", cameraRotZ),
|
||||||
|
}
|
||||||
|
gtw, err := gateway.New(address, &carConfig, &racer, &camera)
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Fatalf("unable to init gateway: %v", err)
|
||||||
|
}
|
||||||
defer gtw.Stop()
|
defer gtw.Stop()
|
||||||
|
|
||||||
msgPub := events.NewMsgPublisher(
|
msgPub := events.NewMsgPublisher(
|
||||||
@@ -59,45 +148,46 @@ func main() {
|
|||||||
topicThrottle,
|
topicThrottle,
|
||||||
)
|
)
|
||||||
defer msgPub.Stop()
|
defer msgPub.Stop()
|
||||||
|
msgPub.Start()
|
||||||
|
|
||||||
cli.HandleExit(gtw)
|
cli.HandleExit(gtw)
|
||||||
|
|
||||||
err = gtw.Start()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("unable to start service: %v", err)
|
|
||||||
}
|
|
||||||
msgPub.Start()
|
|
||||||
|
|
||||||
if topicCtrlSteering != "" {
|
if topicCtrlSteering != "" {
|
||||||
log.Infof("configure mqtt route on steering command")
|
zap.S().Info("configure mqtt route on steering command")
|
||||||
client.AddRoute(topicCtrlSteering, func(client mqtt.Client, message mqtt.Message) {
|
client.Subscribe(topicCtrlSteering, byte(mqttQos), func(client mqtt.Client, message mqtt.Message) {
|
||||||
onSteeringCommand(gtw, message)
|
onSteeringCommand(gtw, message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if topicCtrlThrottle != "" {
|
if topicCtrlThrottle != "" {
|
||||||
log.Infof("configure mqtt route on throttle command")
|
zap.S().Info("configure mqtt route on throttle command")
|
||||||
client.AddRoute(topicCtrlThrottle, func(client mqtt.Client, message mqtt.Message) {
|
client.Subscribe(topicCtrlThrottle, byte(mqttQos), func(client mqtt.Client, message mqtt.Message) {
|
||||||
onThrottleCommand(gtw, message)
|
onThrottleCommand(gtw, message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = gtw.Start()
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Fatalf("unable to start service: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func onSteeringCommand(c *gateway.Gateway, message mqtt.Message) {
|
func onSteeringCommand(c *gateway.Gateway, message mqtt.Message) {
|
||||||
var steeringMsg *events2.SteeringMessage
|
var steeringMsg events2.SteeringMessage
|
||||||
err := proto.Unmarshal(message.Payload(), steeringMsg)
|
err := proto.Unmarshal(message.Payload(), &steeringMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("unable to unmarshal steering msg: %v", err)
|
zap.S().Errorf("unable to unmarshal steering msg: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.WriteSteering(steeringMsg)
|
c.WriteSteering(&steeringMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func onThrottleCommand(c *gateway.Gateway, message mqtt.Message) {
|
func onThrottleCommand(c *gateway.Gateway, message mqtt.Message) {
|
||||||
var throttleMsg *events2.ThrottleMessage
|
var throttleMsg events2.ThrottleMessage
|
||||||
err := proto.Unmarshal(message.Payload(), throttleMsg)
|
err := proto.Unmarshal(message.Payload(), &throttleMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("unable to unmarshal throttle msg: %v", err)
|
zap.S().Errorf("unable to unmarshal throttle msg: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.WriteThrottle(throttleMsg)
|
c.WriteThrottle(&throttleMsg)
|
||||||
}
|
}
|
||||||
|
|||||||
24
go.mod
24
go.mod
@@ -1,12 +1,22 @@
|
|||||||
module github.com/cyrilix/robocar-simulator
|
module github.com/cyrilix/robocar-simulator
|
||||||
|
|
||||||
go 1.15
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/avast/retry-go v2.6.0+incompatible
|
github.com/avast/retry-go v3.0.0+incompatible
|
||||||
github.com/cyrilix/robocar-base v0.1.2
|
github.com/cyrilix/robocar-base v0.1.6
|
||||||
github.com/cyrilix/robocar-protobuf/go v1.0.2
|
github.com/cyrilix/robocar-protobuf/go v1.0.4
|
||||||
github.com/eclipse/paho.mqtt.golang v1.3.1
|
github.com/disintegration/imaging v1.6.2
|
||||||
github.com/golang/protobuf v1.4.3
|
github.com/eclipse/paho.mqtt.golang v1.3.5
|
||||||
github.com/sirupsen/logrus v1.7.0
|
github.com/golang/protobuf v1.5.2
|
||||||
|
go.uber.org/zap v1.19.1
|
||||||
|
google.golang.org/protobuf v1.27.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gorilla/websocket v1.4.2 // indirect
|
||||||
|
go.uber.org/atomic v1.7.0 // indirect
|
||||||
|
go.uber.org/multierr v1.6.0 // indirect
|
||||||
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
99
go.sum
99
go.sum
@@ -2,25 +2,29 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
|||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||||
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
|
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
|
||||||
github.com/avast/retry-go v2.6.0+incompatible h1:FelcMrm7Bxacr1/RM8+/eqkDkmVN7tjlsy51dOzB3LI=
|
github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
|
||||||
github.com/avast/retry-go v2.6.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
||||||
|
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||||
|
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||||
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||||
github.com/cyrilix/robocar-base v0.1.2 h1:bhT5ohhviodCOd5usy013ctwu7ko+IfyDXnYHg7lJ8I=
|
github.com/cyrilix/robocar-base v0.1.6 h1:VVcSZD8DPsha3XDLxRBMvtcd6uC8CcIjqbxG482dxvo=
|
||||||
github.com/cyrilix/robocar-base v0.1.2/go.mod h1:G2SiYNUDAIv+65XWpiHnCXj7Jz2V6pOBLaIrcasGkww=
|
github.com/cyrilix/robocar-base v0.1.6/go.mod h1:m5ov/7hpRHi0yMp2prKafL6UEsM2O71Uea85WR0/jjI=
|
||||||
github.com/cyrilix/robocar-protobuf/go v1.0.2 h1:eWwu7T07uvABh74bWOJa77alUs6VaWNEPd7Zezua2Cs=
|
github.com/cyrilix/robocar-protobuf/go v1.0.4 h1:XTolFYbiKw4gQ2l+z/LMZkLrmAUMzlHcQBzp/czlANo=
|
||||||
github.com/cyrilix/robocar-protobuf/go v1.0.2/go.mod h1:xj7H/a7qpvXgmW1983Fjd143Mz9Yt0C6RCxvB8M6pEM=
|
github.com/cyrilix/robocar-protobuf/go v1.0.4/go.mod h1:1fyGMVm4ZodfYRrbWCEQgtvKyvrhyTBe5zA7/Qeh/H0=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||||
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/eclipse/paho.mqtt.golang v1.3.1 h1:6F5FYb1hxVSZS+p0ji5xBQamc5ltOolTYRy5R15uVmI=
|
github.com/eclipse/paho.mqtt.golang v1.3.5 h1:sWtmgNxYM9P2sP+xEItMozsR3w0cqZFlqnNN1bdl41Y=
|
||||||
github.com/eclipse/paho.mqtt.golang v1.3.1/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc=
|
github.com/eclipse/paho.mqtt.golang v1.3.5/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||||
@@ -35,18 +39,12 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
|
|||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
|
||||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
@@ -57,10 +55,8 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
|
|||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
@@ -74,35 +70,54 @@ github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQ
|
|||||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||||
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
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=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/testcontainers/testcontainers-go v0.9.0/go.mod h1:b22BFXhRbg4PJmeMVWh6ftqjyZHgiIl3w274e9r3C2E=
|
github.com/testcontainers/testcontainers-go v0.9.0/go.mod h1:b22BFXhRbg4PJmeMVWh6ftqjyZHgiIl3w274e9r3C2E=
|
||||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||||
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
|
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||||
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
|
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4=
|
||||||
|
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||||
|
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||||
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
|
go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI=
|
||||||
|
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
|
||||||
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
|
||||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -110,36 +125,44 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180810170437-e96c4e24768d/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180810170437-e96c4e24768d/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
|
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=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gotest.tools v0.0.0-20181223230014-1083505acf35/go.mod h1:R//lfYlUuTOTfblYI3lGoAAAebUdzjvbmQsuB7Ykd90=
|
gotest.tools v0.0.0-20181223230014-1083505acf35/go.mod h1:R//lfYlUuTOTfblYI3lGoAAAebUdzjvbmQsuB7Ykd90=
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/cyrilix/robocar-simulator/pkg/gateway"
|
"github.com/cyrilix/robocar-simulator/pkg/gateway"
|
||||||
mqtt "github.com/eclipse/paho.mqtt.golang"
|
mqtt "github.com/eclipse/paho.mqtt.golang"
|
||||||
"github.com/golang/protobuf/proto"
|
"go.uber.org/zap"
|
||||||
log "github.com/sirupsen/logrus"
|
"google.golang.org/protobuf/proto"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -59,7 +59,7 @@ func (m *MsgPublisher) Stop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *MsgPublisher) listenThrottle() {
|
func (m *MsgPublisher) listenThrottle() {
|
||||||
logr := log.WithField("msg_type", "throttleChan")
|
logr := zap.S().With("msg_type", "throttleChan")
|
||||||
msgChan := m.srcEvents.SubscribeThrottle()
|
msgChan := m.srcEvents.SubscribeThrottle()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@@ -83,7 +83,7 @@ func (m *MsgPublisher) listenThrottle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *MsgPublisher) listenSteering() {
|
func (m *MsgPublisher) listenSteering() {
|
||||||
logr := log.WithField("msg_type", "steeringChan")
|
logr := zap.S().With("msg_type", "steeringChan")
|
||||||
msgChan := m.srcEvents.SubscribeSteering()
|
msgChan := m.srcEvents.SubscribeSteering()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@@ -107,10 +107,16 @@ func (m *MsgPublisher) listenSteering() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *MsgPublisher) listenFrame() {
|
func (m *MsgPublisher) listenFrame() {
|
||||||
logr := log.WithField("msg_type", "frame")
|
logr := zap.S().With("msg_type", "frame")
|
||||||
msgChan := m.srcEvents.SubscribeFrame()
|
msgChan := m.srcEvents.SubscribeFrame()
|
||||||
for {
|
for {
|
||||||
msg := <-msgChan
|
msg := <-msgChan
|
||||||
|
if msg == nil {
|
||||||
|
// channel closed
|
||||||
|
logr.Info("received empty message, channel closed")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
logr.Debugf("new frame %v", msg.Id)
|
||||||
if m.topicFrame == "" {
|
if m.topicFrame == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package events
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/cyrilix/robocar-protobuf/go/events"
|
"github.com/cyrilix/robocar-protobuf/go/events"
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"github.com/golang/protobuf/ptypes/timestamp"
|
"github.com/golang/protobuf/ptypes/timestamp"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,8 +4,74 @@ import (
|
|||||||
"github.com/cyrilix/robocar-protobuf/go/events"
|
"github.com/cyrilix/robocar-protobuf/go/events"
|
||||||
"github.com/cyrilix/robocar-simulator/pkg/simulator"
|
"github.com/cyrilix/robocar-simulator/pkg/simulator"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func TestGateway_Start(t *testing.T) {
|
||||||
|
|
||||||
|
simulatorMock := Gw2SimMock{}
|
||||||
|
err := simulatorMock.listen()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unable to start mock gw: %v", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := simulatorMock.Close(); err != nil {
|
||||||
|
t.Errorf("unable to stop simulator mock: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
carConfig := simulator.CarConfigMsg{
|
||||||
|
MsgType: simulator.MsgTypeCarConfig,
|
||||||
|
BodyStyle: simulator.CarConfigBodyStyleCar01,
|
||||||
|
CarName: "car-test",
|
||||||
|
}
|
||||||
|
camConfig :=simulator.CamConfigMsg{
|
||||||
|
MsgType: simulator.MsgTypeCameraConfig,
|
||||||
|
Fov: "0",
|
||||||
|
FishEyeX: "0",
|
||||||
|
FishEyeY: "0",
|
||||||
|
ImgW: "120",
|
||||||
|
ImgH: "50",
|
||||||
|
ImgD: "0",
|
||||||
|
ImgEnc: simulator.CameraImageEncJpeg,
|
||||||
|
OffsetX: "0",
|
||||||
|
OffsetY: "0",
|
||||||
|
OffsetZ: "0",
|
||||||
|
RotX: "0",
|
||||||
|
}
|
||||||
|
gw, err := New(simulatorMock.Addr(),
|
||||||
|
&carConfig,
|
||||||
|
&simulator.RacerBioMsg{},
|
||||||
|
&camConfig,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to init simulator gateway: %v", err)
|
||||||
|
}
|
||||||
|
go gw.Start()
|
||||||
|
defer gw.Close()
|
||||||
|
|
||||||
|
carNotify := simulatorMock.NotifyCar()
|
||||||
|
select {
|
||||||
|
case <- time.Tick(500 * time.Millisecond):
|
||||||
|
t.Errorf("a car configuration message should be send")
|
||||||
|
case msg := <-carNotify:
|
||||||
|
if *msg != carConfig {
|
||||||
|
t.Errorf("[carConfig] invalid car config: %v, wants %v", &msg, carConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
camNotify := simulatorMock.NotifyCamera()
|
||||||
|
select {
|
||||||
|
case <- time.Tick(500 * time.Millisecond):
|
||||||
|
t.Errorf("a camera configuration message should be send")
|
||||||
|
case msg := <-camNotify:
|
||||||
|
if *msg != camConfig {
|
||||||
|
t.Errorf("[carConfig] invalid camera config: %v, wants %v", &msg, camConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestGateway_WriteSteering(t *testing.T) {
|
func TestGateway_WriteSteering(t *testing.T) {
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
@@ -18,38 +84,38 @@ func TestGateway_WriteSteering(t *testing.T) {
|
|||||||
&events.SteeringMessage{Steering: 0.5, Confidence: 1},
|
&events.SteeringMessage{Steering: 0.5, Confidence: 1},
|
||||||
nil,
|
nil,
|
||||||
simulator.ControlMsg{
|
simulator.ControlMsg{
|
||||||
MsgType: "control",
|
MsgType: simulator.MsgTypeControl,
|
||||||
Steering: 0.5,
|
Steering: "0.50",
|
||||||
Throttle: 0,
|
Throttle: "0.0",
|
||||||
Brake: 0,
|
Brake: "0.0",
|
||||||
}},
|
}},
|
||||||
{"Update steering",
|
{"Update steering",
|
||||||
&events.SteeringMessage{Steering: -0.5, Confidence: 1},
|
&events.SteeringMessage{Steering: -0.5, Confidence: 1},
|
||||||
&simulator.ControlMsg{
|
&simulator.ControlMsg{
|
||||||
MsgType: "control",
|
MsgType: simulator.MsgTypeControl,
|
||||||
Steering: 0.2,
|
Steering: "0.2",
|
||||||
Throttle: 0,
|
Throttle: "0.0",
|
||||||
Brake: 0,
|
Brake: "0.0",
|
||||||
},
|
},
|
||||||
simulator.ControlMsg{
|
simulator.ControlMsg{
|
||||||
MsgType: "control",
|
MsgType: simulator.MsgTypeControl,
|
||||||
Steering: -0.5,
|
Steering: "-0.50",
|
||||||
Throttle: 0,
|
Throttle: "0.0",
|
||||||
Brake: 0,
|
Brake: "0.0",
|
||||||
}},
|
}},
|
||||||
{"Update steering shouldn't erase throttle value",
|
{"Update steering shouldn't erase throttle value",
|
||||||
&events.SteeringMessage{Steering: -0.3, Confidence: 1},
|
&events.SteeringMessage{Steering: -0.3, Confidence: 1},
|
||||||
&simulator.ControlMsg{
|
&simulator.ControlMsg{
|
||||||
MsgType: "control",
|
MsgType: simulator.MsgTypeControl,
|
||||||
Steering: 0.2,
|
Steering: "0.2",
|
||||||
Throttle: 0.6,
|
Throttle: "0.6",
|
||||||
Brake: 0.1,
|
Brake: "0.1",
|
||||||
},
|
},
|
||||||
simulator.ControlMsg{
|
simulator.ControlMsg{
|
||||||
MsgType: "control",
|
MsgType: simulator.MsgTypeControl,
|
||||||
Steering: -0.3,
|
Steering: "-0.30",
|
||||||
Throttle: 0.6,
|
Throttle: "0.6",
|
||||||
Brake: 0.1,
|
Brake: "0.1",
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,19 +130,27 @@ func TestGateway_WriteSteering(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
gw := New(simulatorMock.Addr())
|
gw, err := New(simulatorMock.Addr(),
|
||||||
|
&simulator.CarConfigMsg{MsgType: simulator.MsgTypeCarConfig},
|
||||||
|
&simulator.RacerBioMsg{},
|
||||||
|
&simulator.CamConfigMsg{
|
||||||
|
ImgH: "128",
|
||||||
|
ImgW: "160",
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to init simulator gateway: %v", err)
|
t.Fatalf("unable to init simulator gateway: %v", err)
|
||||||
}
|
}
|
||||||
go gw.Start()
|
//go gw.Start()
|
||||||
defer gw.Close()
|
//defer gw.Close()
|
||||||
|
//<- simulatorMock.NotifyCar()
|
||||||
|
//<- simulatorMock.NotifyCamera()
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
gw.lastControl = c.previousMsg
|
gw.lastControl = c.previousMsg
|
||||||
|
|
||||||
gw.WriteSteering(c.msg)
|
gw.WriteSteering(c.msg)
|
||||||
|
|
||||||
ctrlMsg := <-simulatorMock.Notify()
|
ctrlMsg := <-simulatorMock.NotifyCtrl()
|
||||||
if *ctrlMsg != c.expectedMsg {
|
if *ctrlMsg != c.expectedMsg {
|
||||||
t.Errorf("[%v] bad messge received: %#v, wants %#v", c.name, ctrlMsg, c.expectedMsg)
|
t.Errorf("[%v] bad messge received: %#v, wants %#v", c.name, ctrlMsg, c.expectedMsg)
|
||||||
}
|
}
|
||||||
@@ -95,80 +169,80 @@ func TestGateway_WriteThrottle(t *testing.T) {
|
|||||||
&events.ThrottleMessage{Throttle: 0.5, Confidence: 1},
|
&events.ThrottleMessage{Throttle: 0.5, Confidence: 1},
|
||||||
nil,
|
nil,
|
||||||
simulator.ControlMsg{
|
simulator.ControlMsg{
|
||||||
MsgType: "control",
|
MsgType: simulator.MsgTypeControl,
|
||||||
Steering: 0,
|
Steering: "0.0",
|
||||||
Throttle: 0.5,
|
Throttle: "0.50",
|
||||||
Brake: 0,
|
Brake: "0.0",
|
||||||
}},
|
}},
|
||||||
{"Update Throttle",
|
{"Update Throttle",
|
||||||
&events.ThrottleMessage{Throttle: 0.6, Confidence: 1},
|
&events.ThrottleMessage{Throttle: 0.6, Confidence: 1},
|
||||||
&simulator.ControlMsg{
|
&simulator.ControlMsg{
|
||||||
MsgType: "control",
|
MsgType: simulator.MsgTypeControl,
|
||||||
Steering: 0,
|
Steering: "0",
|
||||||
Throttle: 0.4,
|
Throttle: "0.4",
|
||||||
Brake: 0,
|
Brake: "0",
|
||||||
},
|
},
|
||||||
simulator.ControlMsg{
|
simulator.ControlMsg{
|
||||||
MsgType: "control",
|
MsgType: simulator.MsgTypeControl,
|
||||||
Steering: 0,
|
Steering: "0",
|
||||||
Throttle: 0.6,
|
Throttle: "0.60",
|
||||||
Brake: 0,
|
Brake: "0.0",
|
||||||
}},
|
}},
|
||||||
{"Update steering shouldn't erase throttle value",
|
{"Update steering shouldn't erase throttle value",
|
||||||
&events.ThrottleMessage{Throttle: 0.3, Confidence: 1},
|
&events.ThrottleMessage{Throttle: 0.3, Confidence: 1},
|
||||||
&simulator.ControlMsg{
|
&simulator.ControlMsg{
|
||||||
MsgType: "control",
|
MsgType: simulator.MsgTypeControl,
|
||||||
Steering: 0.2,
|
Steering: "0.2",
|
||||||
Throttle: 0.6,
|
Throttle: "0.6",
|
||||||
Brake: 0.,
|
Brake: "0.0",
|
||||||
},
|
},
|
||||||
simulator.ControlMsg{
|
simulator.ControlMsg{
|
||||||
MsgType: "control",
|
MsgType: simulator.MsgTypeControl,
|
||||||
Steering: 0.2,
|
Steering: "0.2",
|
||||||
Throttle: 0.3,
|
Throttle: "0.30",
|
||||||
Brake: 0.,
|
Brake: "0.0",
|
||||||
}},
|
}},
|
||||||
{"Throttle to brake",
|
{"Throttle to brake",
|
||||||
&events.ThrottleMessage{Throttle: -0.7, Confidence: 1},
|
&events.ThrottleMessage{Throttle: -0.7, Confidence: 1},
|
||||||
&simulator.ControlMsg{
|
&simulator.ControlMsg{
|
||||||
MsgType: "control",
|
MsgType: simulator.MsgTypeControl,
|
||||||
Steering: 0.2,
|
Steering: "0.2",
|
||||||
Throttle: 0.6,
|
Throttle: "0.6",
|
||||||
Brake: 0.,
|
Brake: "0.0",
|
||||||
},
|
},
|
||||||
simulator.ControlMsg{
|
simulator.ControlMsg{
|
||||||
MsgType: "control",
|
MsgType: simulator.MsgTypeControl,
|
||||||
Steering: 0.2,
|
Steering: "0.2",
|
||||||
Throttle: 0.,
|
Throttle: "0.0",
|
||||||
Brake: 0.7,
|
Brake: "0.70",
|
||||||
}},
|
}},
|
||||||
{"Update brake",
|
{"Update brake",
|
||||||
&events.ThrottleMessage{Throttle: -0.2, Confidence: 1},
|
&events.ThrottleMessage{Throttle: -0.2, Confidence: 1},
|
||||||
&simulator.ControlMsg{
|
&simulator.ControlMsg{
|
||||||
MsgType: "control",
|
MsgType: simulator.MsgTypeControl,
|
||||||
Steering: 0.2,
|
Steering: "0.2",
|
||||||
Throttle: 0.,
|
Throttle: "0.0",
|
||||||
Brake: 0.5,
|
Brake: "0.5",
|
||||||
},
|
},
|
||||||
simulator.ControlMsg{
|
simulator.ControlMsg{
|
||||||
MsgType: "control",
|
MsgType: simulator.MsgTypeControl,
|
||||||
Steering: 0.2,
|
Steering: "0.2",
|
||||||
Throttle: 0.,
|
Throttle: "0.0",
|
||||||
Brake: 0.2,
|
Brake: "0.20",
|
||||||
}},
|
}},
|
||||||
{"Brake to throttle",
|
{"Brake to throttle",
|
||||||
&events.ThrottleMessage{Throttle: 0.9, Confidence: 1},
|
&events.ThrottleMessage{Throttle: 0.9, Confidence: 1},
|
||||||
&simulator.ControlMsg{
|
&simulator.ControlMsg{
|
||||||
MsgType: "control",
|
MsgType: simulator.MsgTypeControl,
|
||||||
Steering: 0.2,
|
Steering: "0.2",
|
||||||
Throttle: 0.,
|
Throttle: "0.0",
|
||||||
Brake: 0.4,
|
Brake: "0.4",
|
||||||
},
|
},
|
||||||
simulator.ControlMsg{
|
simulator.ControlMsg{
|
||||||
MsgType: "control",
|
MsgType: simulator.MsgTypeControl,
|
||||||
Steering: 0.2,
|
Steering: "0.2",
|
||||||
Throttle: 0.9,
|
Throttle: "0.90",
|
||||||
Brake: 0.,
|
Brake: "0.0",
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,17 +257,27 @@ func TestGateway_WriteThrottle(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
gw := New(simulatorMock.Addr())
|
gw, err := New(simulatorMock.Addr(), &simulator.CarConfigMsg{MsgType: simulator.MsgTypeCarConfig},
|
||||||
|
&simulator.RacerBioMsg{},
|
||||||
|
&simulator.CamConfigMsg{
|
||||||
|
ImgH: "128",
|
||||||
|
ImgW: "160",
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to init simulator gateway: %v", err)
|
t.Fatalf("unable to init simulator gateway: %v", err)
|
||||||
}
|
}
|
||||||
|
//go gw.Start()
|
||||||
|
|
||||||
|
//<- simulatorMock.NotifyCar()
|
||||||
|
//<- simulatorMock.NotifyCamera()
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
gw.lastControl = c.previousMsg
|
gw.lastControl = c.previousMsg
|
||||||
|
|
||||||
gw.WriteThrottle(c.msg)
|
gw.WriteThrottle(c.msg)
|
||||||
|
|
||||||
ctrlMsg := <-simulatorMock.Notify()
|
ctrlMsg := <-simulatorMock.NotifyCtrl()
|
||||||
if *ctrlMsg != c.expectedMsg {
|
if *ctrlMsg != c.expectedMsg {
|
||||||
t.Errorf("[%v] bad messge received: %#v, wants %#v", c.name, ctrlMsg, c.expectedMsg)
|
t.Errorf("[%v] bad messge received: %#v, wants %#v", c.name, ctrlMsg, c.expectedMsg)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,20 @@ package gateway
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/avast/retry-go"
|
"github.com/avast/retry-go"
|
||||||
"github.com/cyrilix/robocar-protobuf/go/events"
|
"github.com/cyrilix/robocar-protobuf/go/events"
|
||||||
"github.com/cyrilix/robocar-simulator/pkg/simulator"
|
"github.com/cyrilix/robocar-simulator/pkg/simulator"
|
||||||
|
"github.com/disintegration/imaging"
|
||||||
"github.com/golang/protobuf/ptypes/timestamp"
|
"github.com/golang/protobuf/ptypes/timestamp"
|
||||||
log "github.com/sirupsen/logrus"
|
"go.uber.org/zap"
|
||||||
|
"image"
|
||||||
|
"image/jpeg"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -31,117 +36,169 @@ type ThrottleSource interface {
|
|||||||
SubscribeThrottle() <-chan *events.ThrottleMessage
|
SubscribeThrottle() <-chan *events.ThrottleMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(addressSimulator string) *Gateway {
|
func New(addressSimulator string, car *simulator.CarConfigMsg, racer *simulator.RacerBioMsg, camera *simulator.CamConfigMsg) (*Gateway, error) {
|
||||||
l := log.WithField("simulator", addressSimulator)
|
l := zap.S().With("simulator", addressSimulator)
|
||||||
l.Info("run gateway from simulator")
|
l.Info("run gateway from simulator")
|
||||||
|
|
||||||
|
imgHeight, err := strconv.Atoi(camera.ImgH)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid camera configuration, height should be a number: %w", err)
|
||||||
|
}
|
||||||
|
imgWidth, err := strconv.Atoi(camera.ImgW)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid camera configuration, width should be a number: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return &Gateway{
|
return &Gateway{
|
||||||
address: addressSimulator,
|
address: addressSimulator,
|
||||||
log: l,
|
log: l,
|
||||||
frameSubscribers: make(map[chan<- *events.FrameMessage]interface{}),
|
frameSubscribers: make(map[chan<- *events.FrameMessage]interface{}),
|
||||||
steeringSubscribers: make(map[chan<- *events.SteeringMessage]interface{}),
|
steeringSubscribers: make(map[chan<- *events.SteeringMessage]interface{}),
|
||||||
throttleSubscribers: make(map[chan<- *events.ThrottleMessage]interface{}),
|
throttleSubscribers: make(map[chan<- *events.ThrottleMessage]interface{}),
|
||||||
}
|
telemetrySubscribers: make(map[chan *simulator.TelemetryMsg]interface{}),
|
||||||
|
carSubscribers: make(map[chan *simulator.Msg]interface{}),
|
||||||
|
racerSubscribers: make(map[chan *simulator.Msg]interface{}),
|
||||||
|
cameraSubscribers: make(map[chan *simulator.Msg]interface{}),
|
||||||
|
carConfig: car,
|
||||||
|
imgWidth: imgWidth,
|
||||||
|
imgHeight: imgHeight,
|
||||||
|
racer: racer,
|
||||||
|
cameraConfig: camera,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Simulator interface to events gateway frames into events topicFrame */
|
// Gateway is Simulator interface to events gateway frames into events topicFrame
|
||||||
type Gateway struct {
|
type Gateway struct {
|
||||||
cancel chan interface{}
|
cancel chan interface{}
|
||||||
|
|
||||||
address string
|
address string
|
||||||
|
|
||||||
|
muCommand sync.Mutex
|
||||||
muConn sync.Mutex
|
muConn sync.Mutex
|
||||||
conn io.ReadWriteCloser
|
conn io.ReadWriteCloser
|
||||||
|
|
||||||
muControl sync.Mutex
|
muControl sync.Mutex
|
||||||
lastControl *simulator.ControlMsg
|
lastControl *simulator.ControlMsg
|
||||||
|
|
||||||
log *log.Entry
|
log *zap.SugaredLogger
|
||||||
|
|
||||||
frameSubscribers map[chan<- *events.FrameMessage]interface{}
|
frameSubscribers map[chan<- *events.FrameMessage]interface{}
|
||||||
steeringSubscribers map[chan<- *events.SteeringMessage]interface{}
|
steeringSubscribers map[chan<- *events.SteeringMessage]interface{}
|
||||||
throttleSubscribers map[chan<- *events.ThrottleMessage]interface{}
|
throttleSubscribers map[chan<- *events.ThrottleMessage]interface{}
|
||||||
|
|
||||||
|
telemetrySubscribers map[chan *simulator.TelemetryMsg]interface{}
|
||||||
|
carSubscribers map[chan *simulator.Msg]interface{}
|
||||||
|
racerSubscribers map[chan *simulator.Msg]interface{}
|
||||||
|
cameraSubscribers map[chan *simulator.Msg]interface{}
|
||||||
|
|
||||||
|
carConfig *simulator.CarConfigMsg
|
||||||
|
imgHeight int
|
||||||
|
imgWidth int
|
||||||
|
racer *simulator.RacerBioMsg
|
||||||
|
cameraConfig *simulator.CamConfigMsg
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Gateway) Start() error {
|
func (g *Gateway) Start() error {
|
||||||
p.log.Info("connect to simulator")
|
g.log.Info("connect to simulator")
|
||||||
p.cancel = make(chan interface{})
|
g.cancel = make(chan interface{})
|
||||||
msgChan := make(chan *simulator.TelemetryMsg)
|
|
||||||
|
|
||||||
go p.run(msgChan)
|
go g.run()
|
||||||
|
|
||||||
|
|
||||||
|
err := g.writeRacerConfig()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to configure racer to server: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = g.writeCameraConfig()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to configure camera to server: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
err = g.writeCarConfig()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to configure car to server: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msgChan := g.subscribeTelemetryEvents()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case msg := <-msgChan:
|
case msg := <-msgChan:
|
||||||
fr := p.publishFrame(msg)
|
g.log.Debug("try to publish frame")
|
||||||
go p.publishInputSteering(msg, fr)
|
fr, err := g.publishFrame(msg)
|
||||||
go p.publishInputThrottle(msg, fr)
|
if err != nil {
|
||||||
case <-p.cancel:
|
zap.S().Errorf("unable to publish frame, ignore event %v: %v", msg, err)
|
||||||
|
}
|
||||||
|
g.log.Debugf("frame published: %v", fr)
|
||||||
|
go g.publishInputSteering(msg, fr)
|
||||||
|
go g.publishInputThrottle(msg, fr)
|
||||||
|
case <-g.cancel:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Gateway) Stop() {
|
func (g *Gateway) Stop() {
|
||||||
p.log.Info("close simulator gateway")
|
g.log.Info("close simulator gateway")
|
||||||
close(p.cancel)
|
close(g.cancel)
|
||||||
|
|
||||||
if err := p.Close(); err != nil {
|
if err := g.Close(); err != nil {
|
||||||
p.log.Warnf("unexpected error while simulator connection is closed: %v", err)
|
g.log.Warnf("unexpected error while simulator connection is closed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Gateway) Close() error {
|
func (g *Gateway) Close() error {
|
||||||
for c := range p.frameSubscribers {
|
for c := range g.frameSubscribers {
|
||||||
close(c)
|
close(c)
|
||||||
}
|
}
|
||||||
for c := range p.steeringSubscribers {
|
for c := range g.steeringSubscribers {
|
||||||
close(c)
|
close(c)
|
||||||
}
|
}
|
||||||
for c := range p.throttleSubscribers {
|
for c := range g.throttleSubscribers {
|
||||||
close(c)
|
close(c)
|
||||||
}
|
}
|
||||||
if p.conn == nil {
|
if g.conn == nil {
|
||||||
p.log.Warn("no connection to close")
|
g.log.Warn("no connection to close")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err := p.conn.Close(); err != nil {
|
if err := g.conn.Close(); err != nil {
|
||||||
return fmt.Errorf("unable to close connection to simulator: %v", err)
|
return fmt.Errorf("unable to close connection to simulator: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Gateway) run(msgChan chan<- *simulator.TelemetryMsg) {
|
func (g *Gateway) run() {
|
||||||
err := p.connect()
|
err := g.connect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.log.Panicf("unable to connect to simulator: %v", err)
|
g.log.Panicf("unable to connect to simulator: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
reader := bufio.NewReader(p.conn)
|
|
||||||
|
|
||||||
err = retry.Do(
|
err = retry.Do(
|
||||||
func() error { return p.listen(msgChan, reader) },
|
func() error { return g.listen() },
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.log.Errorf("unable to connect to server: %v", err)
|
g.log.Errorf("unable to connect to server: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Gateway) connect() error {
|
func (g *Gateway) connect() error {
|
||||||
p.muConn.Lock()
|
g.muConn.Lock()
|
||||||
defer p.muConn.Unlock()
|
defer g.muConn.Unlock()
|
||||||
|
|
||||||
if p.conn != nil {
|
if g.conn != nil {
|
||||||
// already connected
|
// already connected
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
err := retry.Do(func() error {
|
err := retry.Do(func() error {
|
||||||
p.log.Info("connect to simulator")
|
g.log.Info("connect to simulator")
|
||||||
conn, err := connect(p.address)
|
conn, err := connect(g.address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to connect to simulator at %v", p.address)
|
return fmt.Errorf("unable to connect to simulator at %v", g.address)
|
||||||
}
|
}
|
||||||
p.conn = conn
|
g.conn = conn
|
||||||
p.log.Info("connection success")
|
g.log.Info("connection success")
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
retry.Delay(1*time.Second),
|
retry.Delay(1*time.Second),
|
||||||
@@ -149,30 +206,96 @@ func (p *Gateway) connect() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Gateway) listen(msgChan chan<- *simulator.TelemetryMsg, reader *bufio.Reader) error {
|
func (g *Gateway) listen() error {
|
||||||
|
reader := bufio.NewReader(g.conn)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
rawLine, err := reader.ReadBytes('\n')
|
rawLine, err := reader.ReadBytes('\n')
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
p.log.Info("Connection closed")
|
g.log.Info("Connection closed")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to read response: %v", err)
|
return fmt.Errorf("unable to read response: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg simulator.TelemetryMsg
|
var msg simulator.Msg
|
||||||
err = json.Unmarshal(rawLine, &msg)
|
err = json.Unmarshal(rawLine, &msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.log.Errorf("unable to unmarshal simulator msg '%v': %v", string(rawLine), err)
|
g.log.Errorf("unable to unmarshal simulator msg '%v': %v", string(rawLine), err)
|
||||||
}
|
|
||||||
if "telemetry" != msg.MsgType {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
msgChan <- &msg
|
|
||||||
|
g.log.Debugf("new message of type %v", string(msg.MsgType))
|
||||||
|
switch msg.MsgType {
|
||||||
|
case "":
|
||||||
|
g.log.Debug("ping")
|
||||||
|
case simulator.MsgTypeTelemetry:
|
||||||
|
g.broadcastTelemetryMsg(rawLine)
|
||||||
|
case simulator.MsgTypeCarLoaded:
|
||||||
|
g.log.Debugf("car loaded: %v",string(rawLine))
|
||||||
|
if err := g.writeCarConfig(); err != nil{
|
||||||
|
zap.S().Errorf("unable to send car config: %v", err)
|
||||||
|
}
|
||||||
|
g.broadcastCarMsg(rawLine)
|
||||||
|
case simulator.MsgTypeRacerInfo:
|
||||||
|
g.broadcastRacerMsg(rawLine)
|
||||||
|
default:
|
||||||
|
g.log.Warnf("unmanaged simulator message: %v", string(rawLine))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Gateway) publishFrame(msgSim *simulator.TelemetryMsg) *events.FrameRef {
|
func (g *Gateway) broadcastTelemetryMsg(rawLine []byte) {
|
||||||
|
var tMsg simulator.TelemetryMsg
|
||||||
|
err := json.Unmarshal(rawLine, &tMsg)
|
||||||
|
if err != nil {
|
||||||
|
g.log.Errorf("unable to unmarshal telemetry simulator msg '%v': %v", string(rawLine), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g.log.Debugf("broadcast %d telemetry'", len(g.telemetrySubscribers))
|
||||||
|
for c := range g.telemetrySubscribers {
|
||||||
|
g.log.Debugf("broadcast telemetry ")
|
||||||
|
c <- &tMsg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Gateway) broadcastCarMsg(rawLine []byte) {
|
||||||
|
var tMsg simulator.Msg
|
||||||
|
err := json.Unmarshal(rawLine, &tMsg)
|
||||||
|
if err != nil {
|
||||||
|
g.log.Errorf("unable to unmarshal car simulator msg '%v': %v", string(rawLine), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for c := range g.carSubscribers {
|
||||||
|
c <- &tMsg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Gateway) broadcastRacerMsg(rawLine []byte) {
|
||||||
|
var tMsg simulator.Msg
|
||||||
|
err := json.Unmarshal(rawLine, &tMsg)
|
||||||
|
if err != nil {
|
||||||
|
g.log.Errorf("unable to unmarshal racer simulator msg '%v': %v", string(rawLine), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for c := range g.racerSubscribers {
|
||||||
|
c <- &tMsg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (g *Gateway) broadcastCameraMsg(rawLine []byte) {
|
||||||
|
var tMsg simulator.Msg
|
||||||
|
err := json.Unmarshal(rawLine, &tMsg)
|
||||||
|
if err != nil {
|
||||||
|
g.log.Errorf("unable to unmarshal camera simulator msg '%v': %v", string(rawLine), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for c := range g.cameraSubscribers {
|
||||||
|
c <- &tMsg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Gateway) publishFrame(msgSim *simulator.TelemetryMsg) (*events.FrameRef, error) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
frameRef := &events.FrameRef{
|
frameRef := &events.FrameRef{
|
||||||
Name: "gateway",
|
Name: "gateway",
|
||||||
@@ -182,60 +305,117 @@ func (p *Gateway) publishFrame(msgSim *simulator.TelemetryMsg) *events.FrameRef
|
|||||||
Nanos: int32(now.Nanosecond()),
|
Nanos: int32(now.Nanosecond()),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := &events.FrameMessage{
|
msg := &events.FrameMessage{
|
||||||
Id: frameRef,
|
Id: frameRef,
|
||||||
Frame: msgSim.Image,
|
Frame: msgSim.Image,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("events frame '%v/%v'", msg.Id.Name, msg.Id.Id)
|
img, _, err := image.Decode(bytes.NewReader(msgSim.Image))
|
||||||
for fs := range p.frameSubscribers {
|
if err != nil {
|
||||||
fs <- msg
|
return nil, fmt.Errorf("bad image, skip simulator event: %w", err)
|
||||||
}
|
}
|
||||||
return frameRef
|
if img.Bounds().Dy() != g.imgHeight || img.Bounds().Dx() != g.imgWidth {
|
||||||
|
zap.S().Debugf("resize simulator image from (%vx%v) -> (%dx%d)", img.Bounds().Dx(), img.Bounds().Dy(), g.imgWidth, g.imgHeight)
|
||||||
|
img = imaging.Resize(img, g.imgWidth, g.imgHeight, imaging.NearestNeighbor)
|
||||||
|
var imgBuf bytes.Buffer
|
||||||
|
err = jpeg.Encode(&imgBuf, img, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to encode to peg resized image: %w", err)
|
||||||
|
}
|
||||||
|
msg.Frame = imgBuf.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Gateway) publishInputSteering(msgSim *simulator.TelemetryMsg, frameRef *events.FrameRef) {
|
g.log.Debugf("events frame '%v/%v'", msg.Id.Name, msg.Id.Id)
|
||||||
|
for fs := range g.frameSubscribers {
|
||||||
|
fs <- msg
|
||||||
|
}
|
||||||
|
return frameRef, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Gateway) publishInputSteering(msgSim *simulator.TelemetryMsg, frameRef *events.FrameRef) {
|
||||||
steering := &events.SteeringMessage{
|
steering := &events.SteeringMessage{
|
||||||
FrameRef: frameRef,
|
FrameRef: frameRef,
|
||||||
Steering: float32(msgSim.SteeringAngle),
|
Steering: float32(msgSim.SteeringAngle),
|
||||||
Confidence: 1.0,
|
Confidence: 1.0,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("events steering '%v'", steering.Steering)
|
g.log.Debugf("events steering '%v'", steering.Steering)
|
||||||
for ss := range p.steeringSubscribers {
|
for ss := range g.steeringSubscribers {
|
||||||
ss <- steering
|
ss <- steering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Gateway) publishInputThrottle(msgSim *simulator.TelemetryMsg, frameRef *events.FrameRef) {
|
func (g *Gateway) publishInputThrottle(msgSim *simulator.TelemetryMsg, frameRef *events.FrameRef) {
|
||||||
msg := &events.ThrottleMessage{
|
msg := &events.ThrottleMessage{
|
||||||
FrameRef: frameRef,
|
FrameRef: frameRef,
|
||||||
Throttle: float32(msgSim.Throttle),
|
Throttle: float32(msgSim.Throttle),
|
||||||
Confidence: 1.0,
|
Confidence: 1.0,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("events throttle '%v'", msg.Throttle)
|
g.log.Debugf("events throttle '%v'", msg.Throttle)
|
||||||
for ts := range p.throttleSubscribers {
|
for ts := range g.throttleSubscribers {
|
||||||
ts <- msg
|
ts <- msg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Gateway) SubscribeFrame() <-chan *events.FrameMessage {
|
func (g *Gateway) SubscribeFrame() <-chan *events.FrameMessage {
|
||||||
frameChan := make(chan *events.FrameMessage)
|
frameChan := make(chan *events.FrameMessage)
|
||||||
p.frameSubscribers[frameChan] = struct{}{}
|
g.frameSubscribers[frameChan] = struct{}{}
|
||||||
return frameChan
|
return frameChan
|
||||||
}
|
}
|
||||||
func (p *Gateway) SubscribeSteering() <-chan *events.SteeringMessage {
|
func (g *Gateway) SubscribeSteering() <-chan *events.SteeringMessage {
|
||||||
steeringChan := make(chan *events.SteeringMessage)
|
steeringChan := make(chan *events.SteeringMessage)
|
||||||
p.steeringSubscribers[steeringChan] = struct{}{}
|
g.steeringSubscribers[steeringChan] = struct{}{}
|
||||||
return steeringChan
|
return steeringChan
|
||||||
}
|
}
|
||||||
func (p *Gateway) SubscribeThrottle() <-chan *events.ThrottleMessage {
|
func (g *Gateway) SubscribeThrottle() <-chan *events.ThrottleMessage {
|
||||||
throttleChan := make(chan *events.ThrottleMessage)
|
throttleChan := make(chan *events.ThrottleMessage)
|
||||||
p.throttleSubscribers[throttleChan] = struct{}{}
|
g.throttleSubscribers[throttleChan] = struct{}{}
|
||||||
return throttleChan
|
return throttleChan
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Gateway) subscribeTelemetryEvents() chan *simulator.TelemetryMsg {
|
||||||
|
telemetryChan := make(chan *simulator.TelemetryMsg)
|
||||||
|
g.telemetrySubscribers[telemetryChan] = struct{}{}
|
||||||
|
return telemetryChan
|
||||||
|
}
|
||||||
|
func (g *Gateway) unsubscribeTelemetryEvents(telemetryChan chan *simulator.TelemetryMsg) {
|
||||||
|
delete(g.telemetrySubscribers, telemetryChan)
|
||||||
|
close(telemetryChan)
|
||||||
|
}
|
||||||
|
func (g *Gateway) subscribeCarEvents() chan *simulator.Msg {
|
||||||
|
carChan := make(chan *simulator.Msg)
|
||||||
|
g.carSubscribers[carChan] = struct{}{}
|
||||||
|
return carChan
|
||||||
|
}
|
||||||
|
func (g *Gateway) unsubscribeCarEvents(carChan chan *simulator.Msg) {
|
||||||
|
delete(g.carSubscribers, carChan)
|
||||||
|
close(carChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Gateway) subscribeRacerEvents() chan *simulator.Msg {
|
||||||
|
racerChan := make(chan *simulator.Msg)
|
||||||
|
g.racerSubscribers[racerChan] = struct{}{}
|
||||||
|
return racerChan
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Gateway) unsubscribeRacerEvents(racerChan chan *simulator.Msg) {
|
||||||
|
delete(g.racerSubscribers, racerChan)
|
||||||
|
close(racerChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Gateway) subscribeCameraEvents() chan *simulator.Msg {
|
||||||
|
cameraChan := make(chan *simulator.Msg)
|
||||||
|
g.cameraSubscribers[cameraChan] = struct{}{}
|
||||||
|
return cameraChan
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Gateway) unsubscribeCameraEvents(cameraChan chan *simulator.Msg) {
|
||||||
|
delete(g.cameraSubscribers, cameraChan)
|
||||||
|
close(cameraChan)
|
||||||
|
}
|
||||||
|
|
||||||
var connect = func(address string) (io.ReadWriteCloser, error) {
|
var connect = func(address string) (io.ReadWriteCloser, error) {
|
||||||
conn, err := net.Dial("tcp", address)
|
conn, err := net.Dial("tcp", address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -244,63 +424,152 @@ var connect = func(address string) (io.ReadWriteCloser, error) {
|
|||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Gateway) WriteSteering(message *events.SteeringMessage) {
|
func (g *Gateway) WriteSteering(message *events.SteeringMessage) {
|
||||||
p.muControl.Lock()
|
g.muControl.Lock()
|
||||||
defer p.muControl.Unlock()
|
defer g.muControl.Unlock()
|
||||||
p.initLastControlMsg()
|
g.initLastControlMsg()
|
||||||
|
|
||||||
p.lastControl.Steering = message.Steering
|
g.lastControl.Steering = fmt.Sprintf("%.2f", message.Steering)
|
||||||
p.writeControlCommandToSimulator()
|
zap.S().Debugf("steering updated: %v", g.lastControl.Steering)
|
||||||
|
g.writeControlCommandToSimulator()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Gateway) writeControlCommandToSimulator() {
|
func (g *Gateway) writeControlCommandToSimulator() {
|
||||||
if err := p.connect(); err != nil {
|
content, err := json.Marshal(g.lastControl)
|
||||||
p.log.Errorf("unable to connect to simulator to send control command: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w := bufio.NewWriter(p.conn)
|
|
||||||
content, err := json.Marshal(p.lastControl)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.log.Errorf("unable to marshall control msg \"%#v\": %v", p.lastControl, err)
|
g.log.Errorf("unable to marshall control msg \"%#v\": %v", g.lastControl, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = w.Write(append(content, '\n'))
|
err = g.writeCommand(content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.log.Errorf("unable to write control msg \"%#v\" to simulator: %v", p.lastControl, err)
|
g.log.Errorf("unable to send control command to simulator: %v", err)
|
||||||
return
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Gateway) writeCommand(content []byte) error {
|
||||||
|
g.muCommand.Lock()
|
||||||
|
defer g.muCommand.Unlock()
|
||||||
|
|
||||||
|
if err := g.connect(); err != nil {
|
||||||
|
g.log.Errorf("unable to connect to simulator to send control command: %v", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
g.log.Debugf("write command to simulator: %v", string(content))
|
||||||
|
w := bufio.NewWriter(g.conn)
|
||||||
|
|
||||||
|
_, err := w.Write(content)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to write control msg \"%#v\" to simulator: %v", g.lastControl, err)
|
||||||
}
|
}
|
||||||
err = w.Flush()
|
err = w.Flush()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.log.Errorf("unable to flush control msg \"%#v\" to simulator: %v", p.lastControl, err)
|
return fmt.Errorf("unable to flush control msg \"%#v\" to simulator: %v", g.lastControl, err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Gateway) WriteThrottle(message *events.ThrottleMessage) {
|
func (g *Gateway) WriteThrottle(message *events.ThrottleMessage) {
|
||||||
p.muControl.Lock()
|
g.muControl.Lock()
|
||||||
defer p.muControl.Unlock()
|
defer g.muControl.Unlock()
|
||||||
p.initLastControlMsg()
|
g.initLastControlMsg()
|
||||||
|
|
||||||
if message.Throttle > 0 {
|
if message.Throttle > 0 {
|
||||||
p.lastControl.Throttle = message.Throttle
|
g.lastControl.Throttle = fmt.Sprintf("%.2f", message.Throttle)
|
||||||
p.lastControl.Brake = 0.
|
g.lastControl.Brake = "0.0"
|
||||||
} else {
|
} else {
|
||||||
p.lastControl.Throttle = 0.
|
g.lastControl.Throttle = "0.0"
|
||||||
p.lastControl.Brake = -1 * message.Throttle
|
g.lastControl.Brake = fmt.Sprintf("%.2f", -1*message.Throttle)
|
||||||
}
|
}
|
||||||
|
|
||||||
p.writeControlCommandToSimulator()
|
zap.S().Debugf("throttle updated: %v, brake: %v", g.lastControl.Throttle, g.lastControl.Brake)
|
||||||
|
g.writeControlCommandToSimulator()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Gateway) initLastControlMsg() {
|
func (g *Gateway) initLastControlMsg() {
|
||||||
if p.lastControl != nil {
|
if g.lastControl != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p.lastControl = &simulator.ControlMsg{
|
g.lastControl = &simulator.ControlMsg{
|
||||||
MsgType: "control",
|
MsgType: simulator.MsgTypeControl,
|
||||||
Steering: 0.,
|
Steering: "0.0",
|
||||||
Throttle: 0.,
|
Throttle: "0.0",
|
||||||
Brake: 0.,
|
Brake: "0.0",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Gateway) writeCarConfig() error {
|
||||||
|
carChan := g.subscribeCarEvents()
|
||||||
|
defer g.unsubscribeCarEvents(carChan)
|
||||||
|
|
||||||
|
g.log.Info("Send car configuration")
|
||||||
|
|
||||||
|
content, err := json.Marshal(g.carConfig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to marshall car config msg \"%#v\": %v", g.lastControl, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = g.writeCommand(content)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to send car config to simulator: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case msg := <-carChan:
|
||||||
|
g.log.Infof("Car loaded: %v", msg)
|
||||||
|
case <-time.Tick(250 * time.Millisecond):
|
||||||
|
g.log.Info("no response after car loading")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Gateway) writeRacerConfig() error {
|
||||||
|
racerChan := g.subscribeRacerEvents()
|
||||||
|
defer g.unsubscribeRacerEvents(racerChan)
|
||||||
|
|
||||||
|
g.log.Info("Send racer configuration")
|
||||||
|
|
||||||
|
content, err := json.Marshal(g.racer)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to marshall racer config msg \"%#v\": %v", g.lastControl, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = g.writeCommand(content)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to send racer config to simulator: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case msg := <-racerChan:
|
||||||
|
g.log.Infof("Racer loaded: %v", msg)
|
||||||
|
case <-time.Tick(250 * time.Millisecond):
|
||||||
|
g.log.Info("no response after racer loading")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Gateway) writeCameraConfig() error {
|
||||||
|
cameraChan := g.subscribeCameraEvents()
|
||||||
|
defer g.unsubscribeCameraEvents(cameraChan)
|
||||||
|
|
||||||
|
g.log.Info("Send camera configuration")
|
||||||
|
|
||||||
|
content, err := json.Marshal(g.cameraConfig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to marshall camera config msg \"%#v\": %v", g.lastControl, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = g.writeCommand(content)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to send camera config to simulator: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case msg := <-cameraChan:
|
||||||
|
g.log.Infof("Camera configured: %v", msg)
|
||||||
|
case <-time.Tick(250 * time.Millisecond):
|
||||||
|
g.log.Info("no response after camera loading")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package gateway
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"github.com/cyrilix/robocar-protobuf/go/events"
|
"github.com/cyrilix/robocar-protobuf/go/events"
|
||||||
log "github.com/sirupsen/logrus"
|
"github.com/cyrilix/robocar-simulator/pkg/simulator"
|
||||||
|
"go.uber.org/zap"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -23,7 +25,14 @@ func TestGateway_ListenEvents(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
gw := New(simulatorMock.Addr())
|
gw, err := New(simulatorMock.Addr(),
|
||||||
|
&simulator.CarConfigMsg{MsgType: simulator.MsgTypeCarConfig},
|
||||||
|
&simulator.RacerBioMsg{},
|
||||||
|
&simulator.CamConfigMsg{},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to init test: %v", err)
|
||||||
|
}
|
||||||
go func() {
|
go func() {
|
||||||
err := gw.Start()
|
err := gw.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -40,7 +49,9 @@ func TestGateway_ListenEvents(t *testing.T) {
|
|||||||
throttleChannel := gw.SubscribeThrottle()
|
throttleChannel := gw.SubscribeThrottle()
|
||||||
|
|
||||||
simulatorMock.WaitConnection()
|
simulatorMock.WaitConnection()
|
||||||
log.Trace("read test data")
|
simulatorMock.EmitMsg(fmt.Sprintf("{\"msg_type\": \"%s\"}", simulator.MsgTypeCarLoaded))
|
||||||
|
|
||||||
|
zap.S().Debug("read test data")
|
||||||
testContent, err := ioutil.ReadFile("testdata/msg.json")
|
testContent, err := ioutil.ReadFile("testdata/msg.json")
|
||||||
lines := strings.Split(string(testContent), "\n")
|
lines := strings.Split(string(testContent), "\n")
|
||||||
|
|
||||||
@@ -85,7 +96,7 @@ func TestGateway_ListenEvents(t *testing.T) {
|
|||||||
throttleIdRef = msg.FrameRef
|
throttleIdRef = msg.FrameRef
|
||||||
wg.Done()
|
wg.Done()
|
||||||
case <-finished:
|
case <-finished:
|
||||||
log.Trace("loop ended")
|
zap.S().Debugf("loop ended")
|
||||||
endLoop = true
|
endLoop = true
|
||||||
case <-timeout:
|
case <-timeout:
|
||||||
t.Errorf("not all event are published")
|
t.Errorf("not all event are published")
|
||||||
|
|||||||
@@ -5,23 +5,38 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/cyrilix/robocar-simulator/pkg/simulator"
|
"github.com/cyrilix/robocar-simulator/pkg/simulator"
|
||||||
log "github.com/sirupsen/logrus"
|
"go.uber.org/zap"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Gw2SimMock struct {
|
type Gw2SimMock struct {
|
||||||
initMsgsOnce sync.Once
|
initOnce sync.Once
|
||||||
|
|
||||||
ln net.Listener
|
ln net.Listener
|
||||||
notifyChan chan *simulator.ControlMsg
|
notifyCtrlChan chan *simulator.ControlMsg
|
||||||
initNotifyChan sync.Once
|
notifyCarChan chan *simulator.CarConfigMsg
|
||||||
|
notifyCamChan chan *simulator.CamConfigMsg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Gw2SimMock) Notify() <-chan *simulator.ControlMsg {
|
func (c *Gw2SimMock) init(){
|
||||||
c.initNotifyChan.Do(func() { c.notifyChan = make(chan *simulator.ControlMsg) })
|
c.notifyCtrlChan = make(chan *simulator.ControlMsg)
|
||||||
return c.notifyChan
|
c.notifyCarChan = make(chan *simulator.CarConfigMsg)
|
||||||
|
c.notifyCamChan = make(chan *simulator.CamConfigMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Gw2SimMock) NotifyCtrl() <-chan *simulator.ControlMsg {
|
||||||
|
c.initOnce.Do(c.init)
|
||||||
|
return c.notifyCtrlChan
|
||||||
|
}
|
||||||
|
func (c *Gw2SimMock) NotifyCar() <-chan *simulator.CarConfigMsg {
|
||||||
|
c.initOnce.Do(c.init)
|
||||||
|
return c.notifyCarChan
|
||||||
|
}
|
||||||
|
func (c *Gw2SimMock) NotifyCamera() <-chan *simulator.CamConfigMsg {
|
||||||
|
c.initOnce.Do(c.init)
|
||||||
|
return c.notifyCamChan
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Gw2SimMock) listen() error {
|
func (c *Gw2SimMock) listen() error {
|
||||||
@@ -36,7 +51,7 @@ func (c *Gw2SimMock) listen() error {
|
|||||||
for {
|
for {
|
||||||
conn, err := c.ln.Accept()
|
conn, err := c.ln.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("connection close: %v", err)
|
zap.S().Debugf("connection close: %v", err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
go c.handleConnection(conn)
|
go c.handleConnection(conn)
|
||||||
@@ -50,10 +65,13 @@ func (c *Gw2SimMock) Addr() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Gw2SimMock) handleConnection(conn net.Conn) {
|
func (c *Gw2SimMock) handleConnection(conn net.Conn) {
|
||||||
c.initNotifyChan.Do(func() { c.notifyChan = make(chan *simulator.ControlMsg) })
|
log := zap.S()
|
||||||
|
c.initOnce.Do(c.init)
|
||||||
reader := bufio.NewReader(conn)
|
reader := bufio.NewReader(conn)
|
||||||
|
writer := bufio.NewWriter(conn)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
rawCmd, err := reader.ReadBytes('\n')
|
rawMsg, err := reader.ReadBytes('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
log.Debug("connection closed")
|
log.Debug("connection closed")
|
||||||
@@ -62,26 +80,71 @@ func (c *Gw2SimMock) handleConnection(conn net.Conn) {
|
|||||||
log.Errorf("unable to read request: %v", err)
|
log.Errorf("unable to read request: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var msg simulator.Msg
|
||||||
var msg simulator.ControlMsg
|
err = json.Unmarshal(rawMsg, &msg)
|
||||||
err = json.Unmarshal(rawCmd, &msg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("unable to unmarchal control msg \"%v\": %v", string(rawCmd), err)
|
log.Errorf("unable to unmarshal msg \"%v\": %v", string(rawMsg), err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
switch msg.MsgType {
|
||||||
c.notifyChan <- &msg
|
case simulator.MsgTypeControl:
|
||||||
|
var msgControl simulator.ControlMsg
|
||||||
|
err = json.Unmarshal(rawMsg, &msgControl)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("unable to unmarshal control msg \"%v\": %v", string(rawMsg), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.notifyCtrlChan <- &msgControl
|
||||||
|
case simulator.MsgTypeCarConfig:
|
||||||
|
var msgCar simulator.CarConfigMsg
|
||||||
|
err = json.Unmarshal(rawMsg, &msgCar)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("unable to unmarshal car msg \"%v\": %v", string(rawMsg), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.notifyCarChan <- &msgCar
|
||||||
|
carLoadedMsg := simulator.Msg{MsgType: simulator.MsgTypeCarLoaded}
|
||||||
|
resp, err := json.Marshal(&carLoadedMsg)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("unable to generate car loaded response: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, err = writer.WriteString(string(resp) + "\n")
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("unable to write car loaded response: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = writer.Flush()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("unable to flush car loaded response: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case simulator.MsgTypeCameraConfig:
|
||||||
|
var msgCam simulator.CamConfigMsg
|
||||||
|
err = json.Unmarshal(rawMsg, &msgCam)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("unable to unmarshal camera msg \"%v\": %v", string(rawMsg), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.notifyCamChan <- &msgCam
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Gw2SimMock) Close() error {
|
func (c *Gw2SimMock) Close() error {
|
||||||
log.Debugf("close mock server")
|
zap.S().Debugf("close mock server")
|
||||||
err := c.ln.Close()
|
err := c.ln.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to close mock server: %v", err)
|
return fmt.Errorf("unable to close mock server: %v", err)
|
||||||
}
|
}
|
||||||
if c.notifyChan != nil {
|
if c.notifyCtrlChan != nil {
|
||||||
close(c.notifyChan)
|
close(c.notifyCtrlChan)
|
||||||
|
}
|
||||||
|
if c.notifyCarChan != nil {
|
||||||
|
close(c.notifyCarChan)
|
||||||
|
}
|
||||||
|
if c.notifyCamChan != nil {
|
||||||
|
close(c.notifyCamChan)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -92,7 +155,7 @@ type Sim2GwMock struct {
|
|||||||
conn net.Conn
|
conn net.Conn
|
||||||
writer *bufio.Writer
|
writer *bufio.Writer
|
||||||
newConnection chan net.Conn
|
newConnection chan net.Conn
|
||||||
logger *log.Entry
|
logger *zap.SugaredLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Sim2GwMock) EmitMsg(p string) (err error) {
|
func (c *Sim2GwMock) EmitMsg(p string) (err error) {
|
||||||
@@ -125,7 +188,7 @@ func (c *Sim2GwMock) WaitConnection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Sim2GwMock) Start() error {
|
func (c *Sim2GwMock) Start() error {
|
||||||
c.logger = log.WithField("simulator", "mock")
|
c.logger = zap.S().With("simulator", "mock")
|
||||||
c.newConnection = make(chan net.Conn)
|
c.newConnection = make(chan net.Conn)
|
||||||
ln, err := net.Listen("tcp", "127.0.0.1:")
|
ln, err := net.Listen("tcp", "127.0.0.1:")
|
||||||
c.ln = ln
|
c.ln = ln
|
||||||
|
|||||||
@@ -1,7 +1,22 @@
|
|||||||
package simulator
|
package simulator
|
||||||
|
|
||||||
|
type MsgType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
MsgTypeControl = MsgType("control")
|
||||||
|
MsgTypeTelemetry = MsgType("telemetry")
|
||||||
|
MsgTypeCarConfig = MsgType("car_config")
|
||||||
|
MsgTypeCarLoaded = MsgType("car_loaded")
|
||||||
|
MsgTypeRacerInfo = MsgType("racer_info")
|
||||||
|
MsgTypeCameraConfig = MsgType("cam_config")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Msg struct {
|
||||||
|
MsgType MsgType `json:"msg_type"`
|
||||||
|
}
|
||||||
|
|
||||||
type TelemetryMsg struct {
|
type TelemetryMsg struct {
|
||||||
MsgType string `json:"msg_type"`
|
MsgType MsgType `json:"msg_type"`
|
||||||
SteeringAngle float64 `json:"steering_angle"`
|
SteeringAngle float64 `json:"steering_angle"`
|
||||||
Throttle float64 `json:"throttle"`
|
Throttle float64 `json:"throttle"`
|
||||||
Speed float64 `json:"speed"`
|
Speed float64 `json:"speed"`
|
||||||
@@ -14,10 +29,90 @@ type TelemetryMsg struct {
|
|||||||
Cte float64 `json:"cte"`
|
Cte float64 `json:"cte"`
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Json msg used to control cars. MsgType must be filled with "control" */
|
// ControlMsg is json msg used to control cars. MsgType must be filled with "control"
|
||||||
type ControlMsg struct {
|
type ControlMsg struct {
|
||||||
MsgType string `json:"msg_type"`
|
MsgType MsgType `json:"msg_type"`
|
||||||
Steering float32 `json:"steering"`
|
Steering string `json:"steering"`
|
||||||
Throttle float32 `json:"throttle"`
|
Throttle string `json:"throttle"`
|
||||||
Brake float32 `json:"brake"`
|
Brake string `json:"brake"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetSceneNamesMsg struct {
|
||||||
|
MsgType MsgType `json:"msg_type"`
|
||||||
|
SceneName string `json:"scene_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoadSceneMsg struct {
|
||||||
|
MsgType MsgType `json:"msg_type"`
|
||||||
|
SceneName string `json:"scene_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CarStyle string
|
||||||
|
|
||||||
|
const (
|
||||||
|
CarConfigBodyStyleDonkey = CarStyle("donkey")
|
||||||
|
CarConfigBodyStyleBare = CarStyle("bare")
|
||||||
|
CarConfigBodyStyleCar01 = CarStyle("car01")
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
# body_style = "donkey" | "bare" | "car01" choice of string
|
||||||
|
# body_rgb = (128, 128, 128) tuple of ints
|
||||||
|
# car_name = "string less than 64 char"
|
||||||
|
*/
|
||||||
|
type CarConfigMsg struct {
|
||||||
|
MsgType MsgType `json:"msg_type"`
|
||||||
|
BodyStyle CarStyle `json:"body_style"`
|
||||||
|
BodyR string `json:"body_r"`
|
||||||
|
BodyG string `json:"body_g"`
|
||||||
|
BodyB string `json:"body_b"`
|
||||||
|
CarName string `json:"car_name"`
|
||||||
|
FontSize string `json:"font_size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
# car_name = "string less than 64 char"
|
||||||
|
# guid = "some random string"
|
||||||
|
*/
|
||||||
|
type RacerBioMsg struct {
|
||||||
|
MsgType MsgType `json:"msg_type"`
|
||||||
|
RacerName string `json:"racer_name"`
|
||||||
|
CarName string `json:"car_name"`
|
||||||
|
Bio string `json:"bio"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
Guid string `json:"guid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CameraImageEnc string
|
||||||
|
|
||||||
|
const (
|
||||||
|
CameraImageEncJpeg = CameraImageEnc("JPG")
|
||||||
|
CameraImageEncPng = CameraImageEnc("PNG")
|
||||||
|
CameraImageEncTga = CameraImageEnc("TGA")
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Camera config
|
||||||
|
set any field to Zero to get the default camera setting.
|
||||||
|
offset_x moves camera left/right
|
||||||
|
offset_y moves camera up/down
|
||||||
|
offset_z moves camera forward/back
|
||||||
|
rot_x will rotate the camera
|
||||||
|
with fish_eye_x/y == 0.0 then you get no distortion
|
||||||
|
img_enc can be one of JPG|PNG|TGA
|
||||||
|
*/
|
||||||
|
type CamConfigMsg struct {
|
||||||
|
MsgType MsgType `json:"msg_type"`
|
||||||
|
Fov string `json:"fov"`
|
||||||
|
FishEyeX string `json:"fish_eye_x"`
|
||||||
|
FishEyeY string `json:"fish_eye_y"`
|
||||||
|
ImgW string `json:"img_w"`
|
||||||
|
ImgH string `json:"img_h"`
|
||||||
|
ImgD string `json:"img_d"`
|
||||||
|
ImgEnc CameraImageEnc `json:"img_enc"`
|
||||||
|
OffsetX string `json:"offset_x"`
|
||||||
|
OffsetY string `json:"offset_y"`
|
||||||
|
OffsetZ string `json:"offset_z"`
|
||||||
|
RotX string `json:"rot_x"`
|
||||||
|
RotY string `json:"rot_y"`
|
||||||
|
RotZ string `json:"rot_z"`
|
||||||
}
|
}
|
||||||
|
|||||||
5
vendor/github.com/avast/retry-go/.travis.yml
generated
vendored
5
vendor/github.com/avast/retry-go/.travis.yml
generated
vendored
@@ -1,13 +1,14 @@
|
|||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.6
|
|
||||||
- 1.7
|
|
||||||
- 1.8
|
- 1.8
|
||||||
- 1.9
|
- 1.9
|
||||||
- "1.10"
|
- "1.10"
|
||||||
- 1.11
|
- 1.11
|
||||||
- 1.12
|
- 1.12
|
||||||
|
- 1.13
|
||||||
|
- 1.14
|
||||||
|
- 1.15
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- make setup
|
- make setup
|
||||||
|
|||||||
37
vendor/github.com/avast/retry-go/README.md
generated
vendored
37
vendor/github.com/avast/retry-go/README.md
generated
vendored
@@ -64,6 +64,12 @@ nonintuitive interface (for me)
|
|||||||
|
|
||||||
### BREAKING CHANGES
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
3.0.0
|
||||||
|
|
||||||
|
* `DelayTypeFunc` accepts a new parameter `err` - this breaking change affects
|
||||||
|
only your custom Delay Functions. This change allow [make delay functions based
|
||||||
|
on error](examples/delay_based_on_error_test.go).
|
||||||
|
|
||||||
1.0.2 -> 2.0.0
|
1.0.2 -> 2.0.0
|
||||||
|
|
||||||
* argument of `retry.Delay` is final delay (no multiplication by `retry.Units`
|
* argument of `retry.Delay` is final delay (no multiplication by `retry.Units`
|
||||||
@@ -91,13 +97,14 @@ var (
|
|||||||
DefaultRetryIf = IsRecoverable
|
DefaultRetryIf = IsRecoverable
|
||||||
DefaultDelayType = CombineDelay(BackOffDelay, RandomDelay)
|
DefaultDelayType = CombineDelay(BackOffDelay, RandomDelay)
|
||||||
DefaultLastErrorOnly = false
|
DefaultLastErrorOnly = false
|
||||||
|
DefaultContext = context.Background()
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### func BackOffDelay
|
#### func BackOffDelay
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func BackOffDelay(n uint, config *Config) time.Duration
|
func BackOffDelay(n uint, _ error, config *Config) time.Duration
|
||||||
```
|
```
|
||||||
BackOffDelay is a DelayType which increases delay between consecutive retries
|
BackOffDelay is a DelayType which increases delay between consecutive retries
|
||||||
|
|
||||||
@@ -110,7 +117,7 @@ func Do(retryableFunc RetryableFunc, opts ...Option) error
|
|||||||
#### func FixedDelay
|
#### func FixedDelay
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func FixedDelay(_ uint, config *Config) time.Duration
|
func FixedDelay(_ uint, _ error, config *Config) time.Duration
|
||||||
```
|
```
|
||||||
FixedDelay is a DelayType which keeps delay the same through all iterations
|
FixedDelay is a DelayType which keeps delay the same through all iterations
|
||||||
|
|
||||||
@@ -124,7 +131,7 @@ IsRecoverable checks if error is an instance of `unrecoverableError`
|
|||||||
#### func RandomDelay
|
#### func RandomDelay
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func RandomDelay(_ uint, config *Config) time.Duration
|
func RandomDelay(_ uint, _ error, config *Config) time.Duration
|
||||||
```
|
```
|
||||||
RandomDelay is a DelayType which picks a random delay up to config.maxJitter
|
RandomDelay is a DelayType which picks a random delay up to config.maxJitter
|
||||||
|
|
||||||
@@ -146,9 +153,11 @@ type Config struct {
|
|||||||
#### type DelayTypeFunc
|
#### type DelayTypeFunc
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type DelayTypeFunc func(n uint, config *Config) time.Duration
|
type DelayTypeFunc func(n uint, err error, config *Config) time.Duration
|
||||||
```
|
```
|
||||||
|
|
||||||
|
DelayTypeFunc is called to return the next delay to wait after the retriable
|
||||||
|
function fails on `err` after `n` attempts.
|
||||||
|
|
||||||
#### func CombineDelay
|
#### func CombineDelay
|
||||||
|
|
||||||
@@ -207,6 +216,26 @@ func Attempts(attempts uint) Option
|
|||||||
```
|
```
|
||||||
Attempts set count of retry default is 10
|
Attempts set count of retry default is 10
|
||||||
|
|
||||||
|
#### func Context
|
||||||
|
|
||||||
|
```go
|
||||||
|
func Context(ctx context.Context) Option
|
||||||
|
```
|
||||||
|
Context allow to set context of retry default are Background context
|
||||||
|
|
||||||
|
example of immediately cancellation (maybe it isn't the best example, but it
|
||||||
|
describes behavior enough; I hope)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
retry.Do(
|
||||||
|
func() error {
|
||||||
|
...
|
||||||
|
},
|
||||||
|
retry.Context(ctx),
|
||||||
|
)
|
||||||
|
|
||||||
#### func Delay
|
#### func Delay
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
|||||||
2
vendor/github.com/avast/retry-go/VERSION
generated
vendored
2
vendor/github.com/avast/retry-go/VERSION
generated
vendored
@@ -1 +1 @@
|
|||||||
2.6.0
|
3.0.0
|
||||||
|
|||||||
65
vendor/github.com/avast/retry-go/options.go
generated
vendored
65
vendor/github.com/avast/retry-go/options.go
generated
vendored
@@ -1,6 +1,8 @@
|
|||||||
package retry
|
package retry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -12,7 +14,8 @@ type RetryIfFunc func(error) bool
|
|||||||
// n = count of attempts
|
// n = count of attempts
|
||||||
type OnRetryFunc func(n uint, err error)
|
type OnRetryFunc func(n uint, err error)
|
||||||
|
|
||||||
type DelayTypeFunc func(n uint, config *Config) time.Duration
|
// DelayTypeFunc is called to return the next delay to wait after the retriable function fails on `err` after `n` attempts.
|
||||||
|
type DelayTypeFunc func(n uint, err error, config *Config) time.Duration
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
attempts uint
|
attempts uint
|
||||||
@@ -23,6 +26,9 @@ type Config struct {
|
|||||||
retryIf RetryIfFunc
|
retryIf RetryIfFunc
|
||||||
delayType DelayTypeFunc
|
delayType DelayTypeFunc
|
||||||
lastErrorOnly bool
|
lastErrorOnly bool
|
||||||
|
context context.Context
|
||||||
|
|
||||||
|
maxBackOffN uint
|
||||||
}
|
}
|
||||||
|
|
||||||
// Option represents an option for retry.
|
// Option represents an option for retry.
|
||||||
@@ -76,28 +82,49 @@ func DelayType(delayType DelayTypeFunc) Option {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BackOffDelay is a DelayType which increases delay between consecutive retries
|
// BackOffDelay is a DelayType which increases delay between consecutive retries
|
||||||
func BackOffDelay(n uint, config *Config) time.Duration {
|
func BackOffDelay(n uint, _ error, config *Config) time.Duration {
|
||||||
return config.delay * (1 << n)
|
// 1 << 63 would overflow signed int64 (time.Duration), thus 62.
|
||||||
|
const max uint = 62
|
||||||
|
|
||||||
|
if config.maxBackOffN == 0 {
|
||||||
|
if config.delay <= 0 {
|
||||||
|
config.delay = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
config.maxBackOffN = max - uint(math.Floor(math.Log2(float64(config.delay))))
|
||||||
|
}
|
||||||
|
|
||||||
|
if n > config.maxBackOffN {
|
||||||
|
n = config.maxBackOffN
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.delay << n
|
||||||
}
|
}
|
||||||
|
|
||||||
// FixedDelay is a DelayType which keeps delay the same through all iterations
|
// FixedDelay is a DelayType which keeps delay the same through all iterations
|
||||||
func FixedDelay(_ uint, config *Config) time.Duration {
|
func FixedDelay(_ uint, _ error, config *Config) time.Duration {
|
||||||
return config.delay
|
return config.delay
|
||||||
}
|
}
|
||||||
|
|
||||||
// RandomDelay is a DelayType which picks a random delay up to config.maxJitter
|
// RandomDelay is a DelayType which picks a random delay up to config.maxJitter
|
||||||
func RandomDelay(_ uint, config *Config) time.Duration {
|
func RandomDelay(_ uint, _ error, config *Config) time.Duration {
|
||||||
return time.Duration(rand.Int63n(int64(config.maxJitter)))
|
return time.Duration(rand.Int63n(int64(config.maxJitter)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// CombineDelay is a DelayType the combines all of the specified delays into a new DelayTypeFunc
|
// CombineDelay is a DelayType the combines all of the specified delays into a new DelayTypeFunc
|
||||||
func CombineDelay(delays ...DelayTypeFunc) DelayTypeFunc {
|
func CombineDelay(delays ...DelayTypeFunc) DelayTypeFunc {
|
||||||
return func(n uint, config *Config) time.Duration {
|
const maxInt64 = uint64(math.MaxInt64)
|
||||||
var total time.Duration
|
|
||||||
|
return func(n uint, err error, config *Config) time.Duration {
|
||||||
|
var total uint64
|
||||||
for _, delay := range delays {
|
for _, delay := range delays {
|
||||||
total += delay(n, config)
|
total += uint64(delay(n, err, config))
|
||||||
|
if total > maxInt64 {
|
||||||
|
total = maxInt64
|
||||||
}
|
}
|
||||||
return total
|
}
|
||||||
|
|
||||||
|
return time.Duration(total)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,3 +176,23 @@ func RetryIf(retryIf RetryIfFunc) Option {
|
|||||||
c.retryIf = retryIf
|
c.retryIf = retryIf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Context allow to set context of retry
|
||||||
|
// default are Background context
|
||||||
|
//
|
||||||
|
// example of immediately cancellation (maybe it isn't the best example, but it describes behavior enough; I hope)
|
||||||
|
//
|
||||||
|
// ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
// cancel()
|
||||||
|
//
|
||||||
|
// retry.Do(
|
||||||
|
// func() error {
|
||||||
|
// ...
|
||||||
|
// },
|
||||||
|
// retry.Context(ctx),
|
||||||
|
// )
|
||||||
|
func Context(ctx context.Context) Option {
|
||||||
|
return func(c *Config) {
|
||||||
|
c.context = ctx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
23
vendor/github.com/avast/retry-go/retry.go
generated
vendored
23
vendor/github.com/avast/retry-go/retry.go
generated
vendored
@@ -43,8 +43,14 @@ SEE ALSO
|
|||||||
|
|
||||||
* [matryer/try](https://github.com/matryer/try) - very popular package, nonintuitive interface (for me)
|
* [matryer/try](https://github.com/matryer/try) - very popular package, nonintuitive interface (for me)
|
||||||
|
|
||||||
|
|
||||||
BREAKING CHANGES
|
BREAKING CHANGES
|
||||||
|
|
||||||
|
3.0.0
|
||||||
|
|
||||||
|
* `DelayTypeFunc` accepts a new parameter `err` - this breaking change affects only your custom Delay Functions. This change allow [make delay functions based on error](examples/delay_based_on_error_test.go).
|
||||||
|
|
||||||
|
|
||||||
1.0.2 -> 2.0.0
|
1.0.2 -> 2.0.0
|
||||||
|
|
||||||
* argument of `retry.Delay` is final delay (no multiplication by `retry.Units` anymore)
|
* argument of `retry.Delay` is final delay (no multiplication by `retry.Units` anymore)
|
||||||
@@ -65,6 +71,7 @@ BREAKING CHANGES
|
|||||||
package retry
|
package retry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -81,6 +88,7 @@ var (
|
|||||||
DefaultRetryIf = IsRecoverable
|
DefaultRetryIf = IsRecoverable
|
||||||
DefaultDelayType = CombineDelay(BackOffDelay, RandomDelay)
|
DefaultDelayType = CombineDelay(BackOffDelay, RandomDelay)
|
||||||
DefaultLastErrorOnly = false
|
DefaultLastErrorOnly = false
|
||||||
|
DefaultContext = context.Background()
|
||||||
)
|
)
|
||||||
|
|
||||||
func Do(retryableFunc RetryableFunc, opts ...Option) error {
|
func Do(retryableFunc RetryableFunc, opts ...Option) error {
|
||||||
@@ -95,6 +103,7 @@ func Do(retryableFunc RetryableFunc, opts ...Option) error {
|
|||||||
retryIf: DefaultRetryIf,
|
retryIf: DefaultRetryIf,
|
||||||
delayType: DefaultDelayType,
|
delayType: DefaultDelayType,
|
||||||
lastErrorOnly: DefaultLastErrorOnly,
|
lastErrorOnly: DefaultLastErrorOnly,
|
||||||
|
context: DefaultContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
//apply opts
|
//apply opts
|
||||||
@@ -102,6 +111,10 @@ func Do(retryableFunc RetryableFunc, opts ...Option) error {
|
|||||||
opt(config)
|
opt(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := config.context.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
var errorLog Error
|
var errorLog Error
|
||||||
if !config.lastErrorOnly {
|
if !config.lastErrorOnly {
|
||||||
errorLog = make(Error, config.attempts)
|
errorLog = make(Error, config.attempts)
|
||||||
@@ -127,11 +140,17 @@ func Do(retryableFunc RetryableFunc, opts ...Option) error {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
delayTime := config.delayType(n, config)
|
delayTime := config.delayType(n, err, config)
|
||||||
if config.maxDelay > 0 && delayTime > config.maxDelay {
|
if config.maxDelay > 0 && delayTime > config.maxDelay {
|
||||||
delayTime = config.maxDelay
|
delayTime = config.maxDelay
|
||||||
}
|
}
|
||||||
time.Sleep(delayTime)
|
|
||||||
|
select {
|
||||||
|
case <-time.After(delayTime):
|
||||||
|
case <-config.context.Done():
|
||||||
|
return config.context.Err()
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
14
vendor/github.com/cyrilix/robocar-base/cli/cli.go
generated
vendored
14
vendor/github.com/cyrilix/robocar-base/cli/cli.go
generated
vendored
@@ -5,7 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/cyrilix/robocar-base/service"
|
"github.com/cyrilix/robocar-base/service"
|
||||||
MQTT "github.com/eclipse/paho.mqtt.golang"
|
MQTT "github.com/eclipse/paho.mqtt.golang"
|
||||||
"log"
|
"go.uber.org/zap"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -25,7 +25,7 @@ func SetIntDefaultValueFromEnv(value *int, key string, defaultValue int) error {
|
|||||||
sVal = os.Getenv(key)
|
sVal = os.Getenv(key)
|
||||||
val, err := strconv.Atoi(sVal)
|
val, err := strconv.Atoi(sVal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("unable to convert string to int: %v", err)
|
zap.S().Errorf("unable to convert string to int: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
*value = val
|
*value = val
|
||||||
@@ -40,7 +40,7 @@ func SetFloat64DefaultValueFromEnv(value *float64, key string, defaultValue floa
|
|||||||
sVal = os.Getenv(key)
|
sVal = os.Getenv(key)
|
||||||
val, err := strconv.ParseFloat(sVal, 64)
|
val, err := strconv.ParseFloat(sVal, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("unable to convert string to float: %v", err)
|
zap.S().Errorf("unable to convert string to float: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
*value = val
|
*value = val
|
||||||
@@ -81,7 +81,7 @@ func InitIntFlag(key string, defValue int) int {
|
|||||||
var value int
|
var value int
|
||||||
err := SetIntDefaultValueFromEnv(&value, key, defValue)
|
err := SetIntDefaultValueFromEnv(&value, key, defValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("invalid int value: %v", err)
|
zap.S().Panicf("invalid int value: %v", err)
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,7 @@ func InitFloat64Flag(key string, defValue float64) float64 {
|
|||||||
var value float64
|
var value float64
|
||||||
err := SetFloat64DefaultValueFromEnv(&value, key, defValue)
|
err := SetFloat64DefaultValueFromEnv(&value, key, defValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("invalid value: %v", err)
|
zap.S().Panicf("invalid value: %v", err)
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
@@ -106,8 +106,8 @@ func Connect(uri, username, password, clientId string) (MQTT.Client, error) {
|
|||||||
opts.SetDefaultPublishHandler(
|
opts.SetDefaultPublishHandler(
|
||||||
//define a function for the default message handler
|
//define a function for the default message handler
|
||||||
func(client MQTT.Client, msg MQTT.Message) {
|
func(client MQTT.Client, msg MQTT.Message) {
|
||||||
fmt.Printf("TOPIC: %s\n", msg.Topic())
|
zap.S().Infof("TOPIC: %s", msg.Topic())
|
||||||
fmt.Printf("MSG: %s\n", msg.Payload())
|
zap.S().Infof("MSG: %s", msg.Payload())
|
||||||
})
|
})
|
||||||
|
|
||||||
//create and start a client using the above ClientOptions
|
//create and start a client using the above ClientOptions
|
||||||
|
|||||||
8
vendor/github.com/cyrilix/robocar-base/service/part.go
generated
vendored
8
vendor/github.com/cyrilix/robocar-base/service/part.go
generated
vendored
@@ -3,21 +3,21 @@ package service
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
mqtt "github.com/eclipse/paho.mqtt.golang"
|
mqtt "github.com/eclipse/paho.mqtt.golang"
|
||||||
"log"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func StopService(name string, client mqtt.Client, topics ...string) {
|
func StopService(name string, client mqtt.Client, topics ...string) {
|
||||||
log.Printf("Stop %s service", name)
|
zap.S().Infof("Stop %s service", name)
|
||||||
token := client.Unsubscribe(topics...)
|
token := client.Unsubscribe(topics...)
|
||||||
token.Wait()
|
token.Wait()
|
||||||
if token.Error() != nil {
|
if token.Error() != nil {
|
||||||
log.Printf("unable to unsubscribe service: %v", token.Error())
|
zap.S().Errorf("unable to unsubscribe service: %v", token.Error())
|
||||||
}
|
}
|
||||||
client.Disconnect(50)
|
client.Disconnect(50)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterCallback(client mqtt.Client, topic string, callback mqtt.MessageHandler) error {
|
func RegisterCallback(client mqtt.Client, topic string, callback mqtt.MessageHandler) error {
|
||||||
log.Printf("Register callback on topic %v", topic)
|
zap.S().Infof("Register callback on topic %v", topic)
|
||||||
token := client.Subscribe(topic, 0, callback)
|
token := client.Subscribe(topic, 0, callback)
|
||||||
token.Wait()
|
token.Wait()
|
||||||
if token.Error() != nil {
|
if token.Error() != nil {
|
||||||
|
|||||||
6
vendor/github.com/cyrilix/robocar-protobuf/go/events/events.pb.go
generated
vendored
6
vendor/github.com/cyrilix/robocar-protobuf/go/events/events.pb.go
generated
vendored
@@ -1,6 +1,6 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.25.0-devel
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.12.4
|
// protoc v3.12.4
|
||||||
// source: events/events.proto
|
// source: events/events.proto
|
||||||
|
|
||||||
@@ -966,8 +966,8 @@ var file_events_events_proto_rawDesc = []byte{
|
|||||||
0x50, 0x49, 0x4c, 0x4f, 0x54, 0x10, 0x02, 0x2a, 0x32, 0x0a, 0x0a, 0x54, 0x79, 0x70, 0x65, 0x4f,
|
0x50, 0x49, 0x4c, 0x4f, 0x54, 0x10, 0x02, 0x2a, 0x32, 0x0a, 0x0a, 0x54, 0x79, 0x70, 0x65, 0x4f,
|
||||||
0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4e, 0x59, 0x10, 0x00, 0x12, 0x07,
|
0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x4e, 0x59, 0x10, 0x00, 0x12, 0x07,
|
||||||
0x0a, 0x03, 0x43, 0x41, 0x52, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x42, 0x55, 0x4d, 0x50, 0x10,
|
0x0a, 0x03, 0x43, 0x41, 0x52, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x42, 0x55, 0x4d, 0x50, 0x10,
|
||||||
0x02, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x4c, 0x4f, 0x54, 0x10, 0x03, 0x42, 0x08, 0x5a, 0x06, 0x65,
|
0x02, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x4c, 0x4f, 0x54, 0x10, 0x03, 0x42, 0x0a, 0x5a, 0x08, 0x2e,
|
||||||
0x76, 0x65, 0x6e, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
12
vendor/github.com/disintegration/imaging/.travis.yml
generated
vendored
Normal file
12
vendor/github.com/disintegration/imaging/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- "1.10.x"
|
||||||
|
- "1.11.x"
|
||||||
|
- "1.12.x"
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -v -race -cover
|
||||||
|
- $GOPATH/bin/goveralls -service=travis-ci
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2014 Simon Eskildsen
|
Copyright (c) 2012 Grigory Dryapak
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|||||||
copies of the Software, and to permit persons to whom the Software is
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
furnished to do so, subject to the following conditions:
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
The above copyright notice and this permission notice shall be included in all
|
||||||
all copies or substantial portions of the Software.
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
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
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
THE SOFTWARE.
|
SOFTWARE.
|
||||||
226
vendor/github.com/disintegration/imaging/README.md
generated
vendored
Normal file
226
vendor/github.com/disintegration/imaging/README.md
generated
vendored
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
# Imaging
|
||||||
|
|
||||||
|
[](https://godoc.org/github.com/disintegration/imaging)
|
||||||
|
[](https://travis-ci.org/disintegration/imaging)
|
||||||
|
[](https://coveralls.io/github/disintegration/imaging?branch=master)
|
||||||
|
[](https://goreportcard.com/report/github.com/disintegration/imaging)
|
||||||
|
|
||||||
|
Package imaging provides basic image processing functions (resize, rotate, crop, brightness/contrast adjustments, etc.).
|
||||||
|
|
||||||
|
All the image processing functions provided by the package accept any image type that implements `image.Image` interface
|
||||||
|
as an input, and return a new image of `*image.NRGBA` type (32bit RGBA colors, non-premultiplied alpha).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
go get -u github.com/disintegration/imaging
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
http://godoc.org/github.com/disintegration/imaging
|
||||||
|
|
||||||
|
## Usage examples
|
||||||
|
|
||||||
|
A few usage examples can be found below. See the documentation for the full list of supported functions.
|
||||||
|
|
||||||
|
### Image resizing
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Resize srcImage to size = 128x128px using the Lanczos filter.
|
||||||
|
dstImage128 := imaging.Resize(srcImage, 128, 128, imaging.Lanczos)
|
||||||
|
|
||||||
|
// Resize srcImage to width = 800px preserving the aspect ratio.
|
||||||
|
dstImage800 := imaging.Resize(srcImage, 800, 0, imaging.Lanczos)
|
||||||
|
|
||||||
|
// Scale down srcImage to fit the 800x600px bounding box.
|
||||||
|
dstImageFit := imaging.Fit(srcImage, 800, 600, imaging.Lanczos)
|
||||||
|
|
||||||
|
// Resize and crop the srcImage to fill the 100x100px area.
|
||||||
|
dstImageFill := imaging.Fill(srcImage, 100, 100, imaging.Center, imaging.Lanczos)
|
||||||
|
```
|
||||||
|
|
||||||
|
Imaging supports image resizing using various resampling filters. The most notable ones:
|
||||||
|
- `Lanczos` - A high-quality resampling filter for photographic images yielding sharp results.
|
||||||
|
- `CatmullRom` - A sharp cubic filter that is faster than Lanczos filter while providing similar results.
|
||||||
|
- `MitchellNetravali` - A cubic filter that produces smoother results with less ringing artifacts than CatmullRom.
|
||||||
|
- `Linear` - Bilinear resampling filter, produces smooth output. Faster than cubic filters.
|
||||||
|
- `Box` - Simple and fast averaging filter appropriate for downscaling. When upscaling it's similar to NearestNeighbor.
|
||||||
|
- `NearestNeighbor` - Fastest resampling filter, no antialiasing.
|
||||||
|
|
||||||
|
The full list of supported filters: NearestNeighbor, Box, Linear, Hermite, MitchellNetravali, CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine. Custom filters can be created using ResampleFilter struct.
|
||||||
|
|
||||||
|
**Resampling filters comparison**
|
||||||
|
|
||||||
|
Original image:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The same image resized from 600x400px to 150x100px using different resampling filters.
|
||||||
|
From faster (lower quality) to slower (higher quality):
|
||||||
|
|
||||||
|
Filter | Resize result
|
||||||
|
--------------------------|---------------------------------------------
|
||||||
|
`imaging.NearestNeighbor` | 
|
||||||
|
`imaging.Linear` | 
|
||||||
|
`imaging.CatmullRom` | 
|
||||||
|
`imaging.Lanczos` | 
|
||||||
|
|
||||||
|
|
||||||
|
### Gaussian Blur
|
||||||
|
|
||||||
|
```go
|
||||||
|
dstImage := imaging.Blur(srcImage, 0.5)
|
||||||
|
```
|
||||||
|
|
||||||
|
Sigma parameter allows to control the strength of the blurring effect.
|
||||||
|
|
||||||
|
Original image | Sigma = 0.5 | Sigma = 1.5
|
||||||
|
-----------------------------------|----------------------------------------|---------------------------------------
|
||||||
|
 |  | 
|
||||||
|
|
||||||
|
### Sharpening
|
||||||
|
|
||||||
|
```go
|
||||||
|
dstImage := imaging.Sharpen(srcImage, 0.5)
|
||||||
|
```
|
||||||
|
|
||||||
|
`Sharpen` uses gaussian function internally. Sigma parameter allows to control the strength of the sharpening effect.
|
||||||
|
|
||||||
|
Original image | Sigma = 0.5 | Sigma = 1.5
|
||||||
|
-----------------------------------|-------------------------------------------|------------------------------------------
|
||||||
|
 |  | 
|
||||||
|
|
||||||
|
### Gamma correction
|
||||||
|
|
||||||
|
```go
|
||||||
|
dstImage := imaging.AdjustGamma(srcImage, 0.75)
|
||||||
|
```
|
||||||
|
|
||||||
|
Original image | Gamma = 0.75 | Gamma = 1.25
|
||||||
|
-----------------------------------|------------------------------------------|-----------------------------------------
|
||||||
|
 |  | 
|
||||||
|
|
||||||
|
### Contrast adjustment
|
||||||
|
|
||||||
|
```go
|
||||||
|
dstImage := imaging.AdjustContrast(srcImage, 20)
|
||||||
|
```
|
||||||
|
|
||||||
|
Original image | Contrast = 15 | Contrast = -15
|
||||||
|
-----------------------------------|--------------------------------------------|-------------------------------------------
|
||||||
|
 |  | 
|
||||||
|
|
||||||
|
### Brightness adjustment
|
||||||
|
|
||||||
|
```go
|
||||||
|
dstImage := imaging.AdjustBrightness(srcImage, 20)
|
||||||
|
```
|
||||||
|
|
||||||
|
Original image | Brightness = 10 | Brightness = -10
|
||||||
|
-----------------------------------|----------------------------------------------|---------------------------------------------
|
||||||
|
 |  | 
|
||||||
|
|
||||||
|
### Saturation adjustment
|
||||||
|
|
||||||
|
```go
|
||||||
|
dstImage := imaging.AdjustSaturation(srcImage, 20)
|
||||||
|
```
|
||||||
|
|
||||||
|
Original image | Saturation = 30 | Saturation = -30
|
||||||
|
-----------------------------------|----------------------------------------------|---------------------------------------------
|
||||||
|
 |  | 
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
### Incorrect image orientation after processing (e.g. an image appears rotated after resizing)
|
||||||
|
|
||||||
|
Most probably, the given image contains the EXIF orientation tag.
|
||||||
|
The stadard `image/*` packages do not support loading and saving
|
||||||
|
this kind of information. To fix the issue, try opening images with
|
||||||
|
the `AutoOrientation` decode option. If this option is set to `true`,
|
||||||
|
the image orientation is changed after decoding, according to the
|
||||||
|
orientation tag (if present). Here's the example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
img, err := imaging.Open("test.jpg", imaging.AutoOrientation(true))
|
||||||
|
```
|
||||||
|
|
||||||
|
### What's the difference between `imaging` and `gift` packages?
|
||||||
|
|
||||||
|
[imaging](https://github.com/disintegration/imaging)
|
||||||
|
is designed to be a lightweight and simple image manipulation package.
|
||||||
|
It provides basic image processing functions and a few helper functions
|
||||||
|
such as `Open` and `Save`. It consistently returns *image.NRGBA image
|
||||||
|
type (8 bits per channel, RGBA).
|
||||||
|
|
||||||
|
[gift](https://github.com/disintegration/gift)
|
||||||
|
supports more advanced image processing, for example, sRGB/Linear color
|
||||||
|
space conversions. It also supports different output image types
|
||||||
|
(e.g. 16 bits per channel) and provides easy-to-use API for chaining
|
||||||
|
multiple processing steps together.
|
||||||
|
|
||||||
|
## Example code
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/disintegration/imaging"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Open a test image.
|
||||||
|
src, err := imaging.Open("testdata/flowers.png")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to open image: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crop the original image to 300x300px size using the center anchor.
|
||||||
|
src = imaging.CropAnchor(src, 300, 300, imaging.Center)
|
||||||
|
|
||||||
|
// Resize the cropped image to width = 200px preserving the aspect ratio.
|
||||||
|
src = imaging.Resize(src, 200, 0, imaging.Lanczos)
|
||||||
|
|
||||||
|
// Create a blurred version of the image.
|
||||||
|
img1 := imaging.Blur(src, 5)
|
||||||
|
|
||||||
|
// Create a grayscale version of the image with higher contrast and sharpness.
|
||||||
|
img2 := imaging.Grayscale(src)
|
||||||
|
img2 = imaging.AdjustContrast(img2, 20)
|
||||||
|
img2 = imaging.Sharpen(img2, 2)
|
||||||
|
|
||||||
|
// Create an inverted version of the image.
|
||||||
|
img3 := imaging.Invert(src)
|
||||||
|
|
||||||
|
// Create an embossed version of the image using a convolution filter.
|
||||||
|
img4 := imaging.Convolve3x3(
|
||||||
|
src,
|
||||||
|
[9]float64{
|
||||||
|
-1, -1, 0,
|
||||||
|
-1, 1, 1,
|
||||||
|
0, 1, 1,
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create a new image and paste the four produced images into it.
|
||||||
|
dst := imaging.New(400, 400, color.NRGBA{0, 0, 0, 0})
|
||||||
|
dst = imaging.Paste(dst, img1, image.Pt(0, 0))
|
||||||
|
dst = imaging.Paste(dst, img2, image.Pt(0, 200))
|
||||||
|
dst = imaging.Paste(dst, img3, image.Pt(200, 0))
|
||||||
|
dst = imaging.Paste(dst, img4, image.Pt(200, 200))
|
||||||
|
|
||||||
|
// Save the resulting image as JPEG.
|
||||||
|
err = imaging.Save(dst, "testdata/out_example.jpg")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to save image: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|

|
||||||
253
vendor/github.com/disintegration/imaging/adjust.go
generated
vendored
Normal file
253
vendor/github.com/disintegration/imaging/adjust.go
generated
vendored
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
package imaging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Grayscale produces a grayscale version of the image.
|
||||||
|
func Grayscale(img image.Image) *image.NRGBA {
|
||||||
|
src := newScanner(img)
|
||||||
|
dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
|
||||||
|
parallel(0, src.h, func(ys <-chan int) {
|
||||||
|
for y := range ys {
|
||||||
|
i := y * dst.Stride
|
||||||
|
src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4])
|
||||||
|
for x := 0; x < src.w; x++ {
|
||||||
|
d := dst.Pix[i : i+3 : i+3]
|
||||||
|
r := d[0]
|
||||||
|
g := d[1]
|
||||||
|
b := d[2]
|
||||||
|
f := 0.299*float64(r) + 0.587*float64(g) + 0.114*float64(b)
|
||||||
|
y := uint8(f + 0.5)
|
||||||
|
d[0] = y
|
||||||
|
d[1] = y
|
||||||
|
d[2] = y
|
||||||
|
i += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invert produces an inverted (negated) version of the image.
|
||||||
|
func Invert(img image.Image) *image.NRGBA {
|
||||||
|
src := newScanner(img)
|
||||||
|
dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
|
||||||
|
parallel(0, src.h, func(ys <-chan int) {
|
||||||
|
for y := range ys {
|
||||||
|
i := y * dst.Stride
|
||||||
|
src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4])
|
||||||
|
for x := 0; x < src.w; x++ {
|
||||||
|
d := dst.Pix[i : i+3 : i+3]
|
||||||
|
d[0] = 255 - d[0]
|
||||||
|
d[1] = 255 - d[1]
|
||||||
|
d[2] = 255 - d[2]
|
||||||
|
i += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdjustSaturation changes the saturation of the image using the percentage parameter and returns the adjusted image.
|
||||||
|
// The percentage must be in the range (-100, 100).
|
||||||
|
// The percentage = 0 gives the original image.
|
||||||
|
// The percentage = 100 gives the image with the saturation value doubled for each pixel.
|
||||||
|
// The percentage = -100 gives the image with the saturation value zeroed for each pixel (grayscale).
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
// dstImage = imaging.AdjustSaturation(srcImage, 25) // Increase image saturation by 25%.
|
||||||
|
// dstImage = imaging.AdjustSaturation(srcImage, -10) // Decrease image saturation by 10%.
|
||||||
|
//
|
||||||
|
func AdjustSaturation(img image.Image, percentage float64) *image.NRGBA {
|
||||||
|
percentage = math.Min(math.Max(percentage, -100), 100)
|
||||||
|
multiplier := 1 + percentage/100
|
||||||
|
|
||||||
|
return AdjustFunc(img, func(c color.NRGBA) color.NRGBA {
|
||||||
|
h, s, l := rgbToHSL(c.R, c.G, c.B)
|
||||||
|
s *= multiplier
|
||||||
|
if s > 1 {
|
||||||
|
s = 1
|
||||||
|
}
|
||||||
|
r, g, b := hslToRGB(h, s, l)
|
||||||
|
return color.NRGBA{r, g, b, c.A}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdjustContrast changes the contrast of the image using the percentage parameter and returns the adjusted image.
|
||||||
|
// The percentage must be in range (-100, 100). The percentage = 0 gives the original image.
|
||||||
|
// The percentage = -100 gives solid gray image.
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
//
|
||||||
|
// dstImage = imaging.AdjustContrast(srcImage, -10) // Decrease image contrast by 10%.
|
||||||
|
// dstImage = imaging.AdjustContrast(srcImage, 20) // Increase image contrast by 20%.
|
||||||
|
//
|
||||||
|
func AdjustContrast(img image.Image, percentage float64) *image.NRGBA {
|
||||||
|
percentage = math.Min(math.Max(percentage, -100.0), 100.0)
|
||||||
|
lut := make([]uint8, 256)
|
||||||
|
|
||||||
|
v := (100.0 + percentage) / 100.0
|
||||||
|
for i := 0; i < 256; i++ {
|
||||||
|
switch {
|
||||||
|
case 0 <= v && v <= 1:
|
||||||
|
lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*v) * 255.0)
|
||||||
|
case 1 < v && v < 2:
|
||||||
|
lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*(1/(2.0-v))) * 255.0)
|
||||||
|
default:
|
||||||
|
lut[i] = uint8(float64(i)/255.0+0.5) * 255
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return adjustLUT(img, lut)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdjustBrightness changes the brightness of the image using the percentage parameter and returns the adjusted image.
|
||||||
|
// The percentage must be in range (-100, 100). The percentage = 0 gives the original image.
|
||||||
|
// The percentage = -100 gives solid black image. The percentage = 100 gives solid white image.
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
//
|
||||||
|
// dstImage = imaging.AdjustBrightness(srcImage, -15) // Decrease image brightness by 15%.
|
||||||
|
// dstImage = imaging.AdjustBrightness(srcImage, 10) // Increase image brightness by 10%.
|
||||||
|
//
|
||||||
|
func AdjustBrightness(img image.Image, percentage float64) *image.NRGBA {
|
||||||
|
percentage = math.Min(math.Max(percentage, -100.0), 100.0)
|
||||||
|
lut := make([]uint8, 256)
|
||||||
|
|
||||||
|
shift := 255.0 * percentage / 100.0
|
||||||
|
for i := 0; i < 256; i++ {
|
||||||
|
lut[i] = clamp(float64(i) + shift)
|
||||||
|
}
|
||||||
|
|
||||||
|
return adjustLUT(img, lut)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdjustGamma performs a gamma correction on the image and returns the adjusted image.
|
||||||
|
// Gamma parameter must be positive. Gamma = 1.0 gives the original image.
|
||||||
|
// Gamma less than 1.0 darkens the image and gamma greater than 1.0 lightens it.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// dstImage = imaging.AdjustGamma(srcImage, 0.7)
|
||||||
|
//
|
||||||
|
func AdjustGamma(img image.Image, gamma float64) *image.NRGBA {
|
||||||
|
e := 1.0 / math.Max(gamma, 0.0001)
|
||||||
|
lut := make([]uint8, 256)
|
||||||
|
|
||||||
|
for i := 0; i < 256; i++ {
|
||||||
|
lut[i] = clamp(math.Pow(float64(i)/255.0, e) * 255.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return adjustLUT(img, lut)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdjustSigmoid changes the contrast of the image using a sigmoidal function and returns the adjusted image.
|
||||||
|
// It's a non-linear contrast change useful for photo adjustments as it preserves highlight and shadow detail.
|
||||||
|
// The midpoint parameter is the midpoint of contrast that must be between 0 and 1, typically 0.5.
|
||||||
|
// The factor parameter indicates how much to increase or decrease the contrast, typically in range (-10, 10).
|
||||||
|
// If the factor parameter is positive the image contrast is increased otherwise the contrast is decreased.
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
//
|
||||||
|
// dstImage = imaging.AdjustSigmoid(srcImage, 0.5, 3.0) // Increase the contrast.
|
||||||
|
// dstImage = imaging.AdjustSigmoid(srcImage, 0.5, -3.0) // Decrease the contrast.
|
||||||
|
//
|
||||||
|
func AdjustSigmoid(img image.Image, midpoint, factor float64) *image.NRGBA {
|
||||||
|
if factor == 0 {
|
||||||
|
return Clone(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
lut := make([]uint8, 256)
|
||||||
|
a := math.Min(math.Max(midpoint, 0.0), 1.0)
|
||||||
|
b := math.Abs(factor)
|
||||||
|
sig0 := sigmoid(a, b, 0)
|
||||||
|
sig1 := sigmoid(a, b, 1)
|
||||||
|
e := 1.0e-6
|
||||||
|
|
||||||
|
if factor > 0 {
|
||||||
|
for i := 0; i < 256; i++ {
|
||||||
|
x := float64(i) / 255.0
|
||||||
|
sigX := sigmoid(a, b, x)
|
||||||
|
f := (sigX - sig0) / (sig1 - sig0)
|
||||||
|
lut[i] = clamp(f * 255.0)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i := 0; i < 256; i++ {
|
||||||
|
x := float64(i) / 255.0
|
||||||
|
arg := math.Min(math.Max((sig1-sig0)*x+sig0, e), 1.0-e)
|
||||||
|
f := a - math.Log(1.0/arg-1.0)/b
|
||||||
|
lut[i] = clamp(f * 255.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return adjustLUT(img, lut)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sigmoid(a, b, x float64) float64 {
|
||||||
|
return 1 / (1 + math.Exp(b*(a-x)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// adjustLUT applies the given lookup table to the colors of the image.
|
||||||
|
func adjustLUT(img image.Image, lut []uint8) *image.NRGBA {
|
||||||
|
src := newScanner(img)
|
||||||
|
dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
|
||||||
|
lut = lut[0:256]
|
||||||
|
parallel(0, src.h, func(ys <-chan int) {
|
||||||
|
for y := range ys {
|
||||||
|
i := y * dst.Stride
|
||||||
|
src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4])
|
||||||
|
for x := 0; x < src.w; x++ {
|
||||||
|
d := dst.Pix[i : i+3 : i+3]
|
||||||
|
d[0] = lut[d[0]]
|
||||||
|
d[1] = lut[d[1]]
|
||||||
|
d[2] = lut[d[2]]
|
||||||
|
i += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdjustFunc applies the fn function to each pixel of the img image and returns the adjusted image.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// dstImage = imaging.AdjustFunc(
|
||||||
|
// srcImage,
|
||||||
|
// func(c color.NRGBA) color.NRGBA {
|
||||||
|
// // Shift the red channel by 16.
|
||||||
|
// r := int(c.R) + 16
|
||||||
|
// if r > 255 {
|
||||||
|
// r = 255
|
||||||
|
// }
|
||||||
|
// return color.NRGBA{uint8(r), c.G, c.B, c.A}
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
func AdjustFunc(img image.Image, fn func(c color.NRGBA) color.NRGBA) *image.NRGBA {
|
||||||
|
src := newScanner(img)
|
||||||
|
dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
|
||||||
|
parallel(0, src.h, func(ys <-chan int) {
|
||||||
|
for y := range ys {
|
||||||
|
i := y * dst.Stride
|
||||||
|
src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4])
|
||||||
|
for x := 0; x < src.w; x++ {
|
||||||
|
d := dst.Pix[i : i+4 : i+4]
|
||||||
|
r := d[0]
|
||||||
|
g := d[1]
|
||||||
|
b := d[2]
|
||||||
|
a := d[3]
|
||||||
|
c := fn(color.NRGBA{r, g, b, a})
|
||||||
|
d[0] = c.R
|
||||||
|
d[1] = c.G
|
||||||
|
d[2] = c.B
|
||||||
|
d[3] = c.A
|
||||||
|
i += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return dst
|
||||||
|
}
|
||||||
148
vendor/github.com/disintegration/imaging/convolution.go
generated
vendored
Normal file
148
vendor/github.com/disintegration/imaging/convolution.go
generated
vendored
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
package imaging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConvolveOptions are convolution parameters.
|
||||||
|
type ConvolveOptions struct {
|
||||||
|
// If Normalize is true the kernel is normalized before convolution.
|
||||||
|
Normalize bool
|
||||||
|
|
||||||
|
// If Abs is true the absolute value of each color channel is taken after convolution.
|
||||||
|
Abs bool
|
||||||
|
|
||||||
|
// Bias is added to each color channel value after convolution.
|
||||||
|
Bias int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convolve3x3 convolves the image with the specified 3x3 convolution kernel.
|
||||||
|
// Default parameters are used if a nil *ConvolveOptions is passed.
|
||||||
|
func Convolve3x3(img image.Image, kernel [9]float64, options *ConvolveOptions) *image.NRGBA {
|
||||||
|
return convolve(img, kernel[:], options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convolve5x5 convolves the image with the specified 5x5 convolution kernel.
|
||||||
|
// Default parameters are used if a nil *ConvolveOptions is passed.
|
||||||
|
func Convolve5x5(img image.Image, kernel [25]float64, options *ConvolveOptions) *image.NRGBA {
|
||||||
|
return convolve(img, kernel[:], options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func convolve(img image.Image, kernel []float64, options *ConvolveOptions) *image.NRGBA {
|
||||||
|
src := toNRGBA(img)
|
||||||
|
w := src.Bounds().Max.X
|
||||||
|
h := src.Bounds().Max.Y
|
||||||
|
dst := image.NewNRGBA(image.Rect(0, 0, w, h))
|
||||||
|
|
||||||
|
if w < 1 || h < 1 {
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
if options == nil {
|
||||||
|
options = &ConvolveOptions{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.Normalize {
|
||||||
|
normalizeKernel(kernel)
|
||||||
|
}
|
||||||
|
|
||||||
|
type coef struct {
|
||||||
|
x, y int
|
||||||
|
k float64
|
||||||
|
}
|
||||||
|
var coefs []coef
|
||||||
|
var m int
|
||||||
|
|
||||||
|
switch len(kernel) {
|
||||||
|
case 9:
|
||||||
|
m = 1
|
||||||
|
case 25:
|
||||||
|
m = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for y := -m; y <= m; y++ {
|
||||||
|
for x := -m; x <= m; x++ {
|
||||||
|
if kernel[i] != 0 {
|
||||||
|
coefs = append(coefs, coef{x: x, y: y, k: kernel[i]})
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parallel(0, h, func(ys <-chan int) {
|
||||||
|
for y := range ys {
|
||||||
|
for x := 0; x < w; x++ {
|
||||||
|
var r, g, b float64
|
||||||
|
for _, c := range coefs {
|
||||||
|
ix := x + c.x
|
||||||
|
if ix < 0 {
|
||||||
|
ix = 0
|
||||||
|
} else if ix >= w {
|
||||||
|
ix = w - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
iy := y + c.y
|
||||||
|
if iy < 0 {
|
||||||
|
iy = 0
|
||||||
|
} else if iy >= h {
|
||||||
|
iy = h - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
off := iy*src.Stride + ix*4
|
||||||
|
s := src.Pix[off : off+3 : off+3]
|
||||||
|
r += float64(s[0]) * c.k
|
||||||
|
g += float64(s[1]) * c.k
|
||||||
|
b += float64(s[2]) * c.k
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.Abs {
|
||||||
|
if r < 0 {
|
||||||
|
r = -r
|
||||||
|
}
|
||||||
|
if g < 0 {
|
||||||
|
g = -g
|
||||||
|
}
|
||||||
|
if b < 0 {
|
||||||
|
b = -b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.Bias != 0 {
|
||||||
|
r += float64(options.Bias)
|
||||||
|
g += float64(options.Bias)
|
||||||
|
b += float64(options.Bias)
|
||||||
|
}
|
||||||
|
|
||||||
|
srcOff := y*src.Stride + x*4
|
||||||
|
dstOff := y*dst.Stride + x*4
|
||||||
|
d := dst.Pix[dstOff : dstOff+4 : dstOff+4]
|
||||||
|
d[0] = clamp(r)
|
||||||
|
d[1] = clamp(g)
|
||||||
|
d[2] = clamp(b)
|
||||||
|
d[3] = src.Pix[srcOff+3]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeKernel(kernel []float64) {
|
||||||
|
var sum, sumpos float64
|
||||||
|
for i := range kernel {
|
||||||
|
sum += kernel[i]
|
||||||
|
if kernel[i] > 0 {
|
||||||
|
sumpos += kernel[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sum != 0 {
|
||||||
|
for i := range kernel {
|
||||||
|
kernel[i] /= sum
|
||||||
|
}
|
||||||
|
} else if sumpos != 0 {
|
||||||
|
for i := range kernel {
|
||||||
|
kernel[i] /= sumpos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
vendor/github.com/disintegration/imaging/doc.go
generated
vendored
Normal file
7
vendor/github.com/disintegration/imaging/doc.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/*
|
||||||
|
Package imaging provides basic image processing functions (resize, rotate, crop, brightness/contrast adjustments, etc.).
|
||||||
|
|
||||||
|
All the image processing functions provided by the package accept any image type that implements image.Image interface
|
||||||
|
as an input, and return a new image of *image.NRGBA type (32bit RGBA colors, non-premultiplied alpha).
|
||||||
|
*/
|
||||||
|
package imaging
|
||||||
169
vendor/github.com/disintegration/imaging/effects.go
generated
vendored
Normal file
169
vendor/github.com/disintegration/imaging/effects.go
generated
vendored
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
package imaging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
func gaussianBlurKernel(x, sigma float64) float64 {
|
||||||
|
return math.Exp(-(x*x)/(2*sigma*sigma)) / (sigma * math.Sqrt(2*math.Pi))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blur produces a blurred version of the image using a Gaussian function.
|
||||||
|
// Sigma parameter must be positive and indicates how much the image will be blurred.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// dstImage := imaging.Blur(srcImage, 3.5)
|
||||||
|
//
|
||||||
|
func Blur(img image.Image, sigma float64) *image.NRGBA {
|
||||||
|
if sigma <= 0 {
|
||||||
|
return Clone(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
radius := int(math.Ceil(sigma * 3.0))
|
||||||
|
kernel := make([]float64, radius+1)
|
||||||
|
|
||||||
|
for i := 0; i <= radius; i++ {
|
||||||
|
kernel[i] = gaussianBlurKernel(float64(i), sigma)
|
||||||
|
}
|
||||||
|
|
||||||
|
return blurVertical(blurHorizontal(img, kernel), kernel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func blurHorizontal(img image.Image, kernel []float64) *image.NRGBA {
|
||||||
|
src := newScanner(img)
|
||||||
|
dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
|
||||||
|
radius := len(kernel) - 1
|
||||||
|
|
||||||
|
parallel(0, src.h, func(ys <-chan int) {
|
||||||
|
scanLine := make([]uint8, src.w*4)
|
||||||
|
scanLineF := make([]float64, len(scanLine))
|
||||||
|
for y := range ys {
|
||||||
|
src.scan(0, y, src.w, y+1, scanLine)
|
||||||
|
for i, v := range scanLine {
|
||||||
|
scanLineF[i] = float64(v)
|
||||||
|
}
|
||||||
|
for x := 0; x < src.w; x++ {
|
||||||
|
min := x - radius
|
||||||
|
if min < 0 {
|
||||||
|
min = 0
|
||||||
|
}
|
||||||
|
max := x + radius
|
||||||
|
if max > src.w-1 {
|
||||||
|
max = src.w - 1
|
||||||
|
}
|
||||||
|
var r, g, b, a, wsum float64
|
||||||
|
for ix := min; ix <= max; ix++ {
|
||||||
|
i := ix * 4
|
||||||
|
weight := kernel[absint(x-ix)]
|
||||||
|
wsum += weight
|
||||||
|
s := scanLineF[i : i+4 : i+4]
|
||||||
|
wa := s[3] * weight
|
||||||
|
r += s[0] * wa
|
||||||
|
g += s[1] * wa
|
||||||
|
b += s[2] * wa
|
||||||
|
a += wa
|
||||||
|
}
|
||||||
|
if a != 0 {
|
||||||
|
aInv := 1 / a
|
||||||
|
j := y*dst.Stride + x*4
|
||||||
|
d := dst.Pix[j : j+4 : j+4]
|
||||||
|
d[0] = clamp(r * aInv)
|
||||||
|
d[1] = clamp(g * aInv)
|
||||||
|
d[2] = clamp(b * aInv)
|
||||||
|
d[3] = clamp(a / wsum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
func blurVertical(img image.Image, kernel []float64) *image.NRGBA {
|
||||||
|
src := newScanner(img)
|
||||||
|
dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
|
||||||
|
radius := len(kernel) - 1
|
||||||
|
|
||||||
|
parallel(0, src.w, func(xs <-chan int) {
|
||||||
|
scanLine := make([]uint8, src.h*4)
|
||||||
|
scanLineF := make([]float64, len(scanLine))
|
||||||
|
for x := range xs {
|
||||||
|
src.scan(x, 0, x+1, src.h, scanLine)
|
||||||
|
for i, v := range scanLine {
|
||||||
|
scanLineF[i] = float64(v)
|
||||||
|
}
|
||||||
|
for y := 0; y < src.h; y++ {
|
||||||
|
min := y - radius
|
||||||
|
if min < 0 {
|
||||||
|
min = 0
|
||||||
|
}
|
||||||
|
max := y + radius
|
||||||
|
if max > src.h-1 {
|
||||||
|
max = src.h - 1
|
||||||
|
}
|
||||||
|
var r, g, b, a, wsum float64
|
||||||
|
for iy := min; iy <= max; iy++ {
|
||||||
|
i := iy * 4
|
||||||
|
weight := kernel[absint(y-iy)]
|
||||||
|
wsum += weight
|
||||||
|
s := scanLineF[i : i+4 : i+4]
|
||||||
|
wa := s[3] * weight
|
||||||
|
r += s[0] * wa
|
||||||
|
g += s[1] * wa
|
||||||
|
b += s[2] * wa
|
||||||
|
a += wa
|
||||||
|
}
|
||||||
|
if a != 0 {
|
||||||
|
aInv := 1 / a
|
||||||
|
j := y*dst.Stride + x*4
|
||||||
|
d := dst.Pix[j : j+4 : j+4]
|
||||||
|
d[0] = clamp(r * aInv)
|
||||||
|
d[1] = clamp(g * aInv)
|
||||||
|
d[2] = clamp(b * aInv)
|
||||||
|
d[3] = clamp(a / wsum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sharpen produces a sharpened version of the image.
|
||||||
|
// Sigma parameter must be positive and indicates how much the image will be sharpened.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// dstImage := imaging.Sharpen(srcImage, 3.5)
|
||||||
|
//
|
||||||
|
func Sharpen(img image.Image, sigma float64) *image.NRGBA {
|
||||||
|
if sigma <= 0 {
|
||||||
|
return Clone(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
src := newScanner(img)
|
||||||
|
dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
|
||||||
|
blurred := Blur(img, sigma)
|
||||||
|
|
||||||
|
parallel(0, src.h, func(ys <-chan int) {
|
||||||
|
scanLine := make([]uint8, src.w*4)
|
||||||
|
for y := range ys {
|
||||||
|
src.scan(0, y, src.w, y+1, scanLine)
|
||||||
|
j := y * dst.Stride
|
||||||
|
for i := 0; i < src.w*4; i++ {
|
||||||
|
val := int(scanLine[i])<<1 - int(blurred.Pix[j])
|
||||||
|
if val < 0 {
|
||||||
|
val = 0
|
||||||
|
} else if val > 0xff {
|
||||||
|
val = 0xff
|
||||||
|
}
|
||||||
|
dst.Pix[j] = uint8(val)
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return dst
|
||||||
|
}
|
||||||
52
vendor/github.com/disintegration/imaging/histogram.go
generated
vendored
Normal file
52
vendor/github.com/disintegration/imaging/histogram.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package imaging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Histogram returns a normalized histogram of an image.
|
||||||
|
//
|
||||||
|
// Resulting histogram is represented as an array of 256 floats, where
|
||||||
|
// histogram[i] is a probability of a pixel being of a particular luminance i.
|
||||||
|
func Histogram(img image.Image) [256]float64 {
|
||||||
|
var mu sync.Mutex
|
||||||
|
var histogram [256]float64
|
||||||
|
var total float64
|
||||||
|
|
||||||
|
src := newScanner(img)
|
||||||
|
if src.w == 0 || src.h == 0 {
|
||||||
|
return histogram
|
||||||
|
}
|
||||||
|
|
||||||
|
parallel(0, src.h, func(ys <-chan int) {
|
||||||
|
var tmpHistogram [256]float64
|
||||||
|
var tmpTotal float64
|
||||||
|
scanLine := make([]uint8, src.w*4)
|
||||||
|
for y := range ys {
|
||||||
|
src.scan(0, y, src.w, y+1, scanLine)
|
||||||
|
i := 0
|
||||||
|
for x := 0; x < src.w; x++ {
|
||||||
|
s := scanLine[i : i+3 : i+3]
|
||||||
|
r := s[0]
|
||||||
|
g := s[1]
|
||||||
|
b := s[2]
|
||||||
|
y := 0.299*float32(r) + 0.587*float32(g) + 0.114*float32(b)
|
||||||
|
tmpHistogram[int(y+0.5)]++
|
||||||
|
tmpTotal++
|
||||||
|
i += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mu.Lock()
|
||||||
|
for i := 0; i < 256; i++ {
|
||||||
|
histogram[i] += tmpHistogram[i]
|
||||||
|
}
|
||||||
|
total += tmpTotal
|
||||||
|
mu.Unlock()
|
||||||
|
})
|
||||||
|
|
||||||
|
for i := 0; i < 256; i++ {
|
||||||
|
histogram[i] = histogram[i] / total
|
||||||
|
}
|
||||||
|
return histogram
|
||||||
|
}
|
||||||
444
vendor/github.com/disintegration/imaging/io.go
generated
vendored
Normal file
444
vendor/github.com/disintegration/imaging/io.go
generated
vendored
Normal file
@@ -0,0 +1,444 @@
|
|||||||
|
package imaging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"image"
|
||||||
|
"image/draw"
|
||||||
|
"image/gif"
|
||||||
|
"image/jpeg"
|
||||||
|
"image/png"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/image/bmp"
|
||||||
|
"golang.org/x/image/tiff"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fileSystem interface {
|
||||||
|
Create(string) (io.WriteCloser, error)
|
||||||
|
Open(string) (io.ReadCloser, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type localFS struct{}
|
||||||
|
|
||||||
|
func (localFS) Create(name string) (io.WriteCloser, error) { return os.Create(name) }
|
||||||
|
func (localFS) Open(name string) (io.ReadCloser, error) { return os.Open(name) }
|
||||||
|
|
||||||
|
var fs fileSystem = localFS{}
|
||||||
|
|
||||||
|
type decodeConfig struct {
|
||||||
|
autoOrientation bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultDecodeConfig = decodeConfig{
|
||||||
|
autoOrientation: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeOption sets an optional parameter for the Decode and Open functions.
|
||||||
|
type DecodeOption func(*decodeConfig)
|
||||||
|
|
||||||
|
// AutoOrientation returns a DecodeOption that sets the auto-orientation mode.
|
||||||
|
// If auto-orientation is enabled, the image will be transformed after decoding
|
||||||
|
// according to the EXIF orientation tag (if present). By default it's disabled.
|
||||||
|
func AutoOrientation(enabled bool) DecodeOption {
|
||||||
|
return func(c *decodeConfig) {
|
||||||
|
c.autoOrientation = enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode reads an image from r.
|
||||||
|
func Decode(r io.Reader, opts ...DecodeOption) (image.Image, error) {
|
||||||
|
cfg := defaultDecodeConfig
|
||||||
|
for _, option := range opts {
|
||||||
|
option(&cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.autoOrientation {
|
||||||
|
img, _, err := image.Decode(r)
|
||||||
|
return img, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var orient orientation
|
||||||
|
pr, pw := io.Pipe()
|
||||||
|
r = io.TeeReader(r, pw)
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
orient = readOrientation(pr)
|
||||||
|
io.Copy(ioutil.Discard, pr)
|
||||||
|
}()
|
||||||
|
|
||||||
|
img, _, err := image.Decode(r)
|
||||||
|
pw.Close()
|
||||||
|
<-done
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fixOrientation(img, orient), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open loads an image from file.
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
//
|
||||||
|
// // Load an image from file.
|
||||||
|
// img, err := imaging.Open("test.jpg")
|
||||||
|
//
|
||||||
|
// // Load an image and transform it depending on the EXIF orientation tag (if present).
|
||||||
|
// img, err := imaging.Open("test.jpg", imaging.AutoOrientation(true))
|
||||||
|
//
|
||||||
|
func Open(filename string, opts ...DecodeOption) (image.Image, error) {
|
||||||
|
file, err := fs.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
return Decode(file, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format is an image file format.
|
||||||
|
type Format int
|
||||||
|
|
||||||
|
// Image file formats.
|
||||||
|
const (
|
||||||
|
JPEG Format = iota
|
||||||
|
PNG
|
||||||
|
GIF
|
||||||
|
TIFF
|
||||||
|
BMP
|
||||||
|
)
|
||||||
|
|
||||||
|
var formatExts = map[string]Format{
|
||||||
|
"jpg": JPEG,
|
||||||
|
"jpeg": JPEG,
|
||||||
|
"png": PNG,
|
||||||
|
"gif": GIF,
|
||||||
|
"tif": TIFF,
|
||||||
|
"tiff": TIFF,
|
||||||
|
"bmp": BMP,
|
||||||
|
}
|
||||||
|
|
||||||
|
var formatNames = map[Format]string{
|
||||||
|
JPEG: "JPEG",
|
||||||
|
PNG: "PNG",
|
||||||
|
GIF: "GIF",
|
||||||
|
TIFF: "TIFF",
|
||||||
|
BMP: "BMP",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Format) String() string {
|
||||||
|
return formatNames[f]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrUnsupportedFormat means the given image format is not supported.
|
||||||
|
var ErrUnsupportedFormat = errors.New("imaging: unsupported image format")
|
||||||
|
|
||||||
|
// FormatFromExtension parses image format from filename extension:
|
||||||
|
// "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported.
|
||||||
|
func FormatFromExtension(ext string) (Format, error) {
|
||||||
|
if f, ok := formatExts[strings.ToLower(strings.TrimPrefix(ext, "."))]; ok {
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
return -1, ErrUnsupportedFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatFromFilename parses image format from filename:
|
||||||
|
// "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported.
|
||||||
|
func FormatFromFilename(filename string) (Format, error) {
|
||||||
|
ext := filepath.Ext(filename)
|
||||||
|
return FormatFromExtension(ext)
|
||||||
|
}
|
||||||
|
|
||||||
|
type encodeConfig struct {
|
||||||
|
jpegQuality int
|
||||||
|
gifNumColors int
|
||||||
|
gifQuantizer draw.Quantizer
|
||||||
|
gifDrawer draw.Drawer
|
||||||
|
pngCompressionLevel png.CompressionLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultEncodeConfig = encodeConfig{
|
||||||
|
jpegQuality: 95,
|
||||||
|
gifNumColors: 256,
|
||||||
|
gifQuantizer: nil,
|
||||||
|
gifDrawer: nil,
|
||||||
|
pngCompressionLevel: png.DefaultCompression,
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeOption sets an optional parameter for the Encode and Save functions.
|
||||||
|
type EncodeOption func(*encodeConfig)
|
||||||
|
|
||||||
|
// JPEGQuality returns an EncodeOption that sets the output JPEG quality.
|
||||||
|
// Quality ranges from 1 to 100 inclusive, higher is better. Default is 95.
|
||||||
|
func JPEGQuality(quality int) EncodeOption {
|
||||||
|
return func(c *encodeConfig) {
|
||||||
|
c.jpegQuality = quality
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GIFNumColors returns an EncodeOption that sets the maximum number of colors
|
||||||
|
// used in the GIF-encoded image. It ranges from 1 to 256. Default is 256.
|
||||||
|
func GIFNumColors(numColors int) EncodeOption {
|
||||||
|
return func(c *encodeConfig) {
|
||||||
|
c.gifNumColors = numColors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GIFQuantizer returns an EncodeOption that sets the quantizer that is used to produce
|
||||||
|
// a palette of the GIF-encoded image.
|
||||||
|
func GIFQuantizer(quantizer draw.Quantizer) EncodeOption {
|
||||||
|
return func(c *encodeConfig) {
|
||||||
|
c.gifQuantizer = quantizer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GIFDrawer returns an EncodeOption that sets the drawer that is used to convert
|
||||||
|
// the source image to the desired palette of the GIF-encoded image.
|
||||||
|
func GIFDrawer(drawer draw.Drawer) EncodeOption {
|
||||||
|
return func(c *encodeConfig) {
|
||||||
|
c.gifDrawer = drawer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PNGCompressionLevel returns an EncodeOption that sets the compression level
|
||||||
|
// of the PNG-encoded image. Default is png.DefaultCompression.
|
||||||
|
func PNGCompressionLevel(level png.CompressionLevel) EncodeOption {
|
||||||
|
return func(c *encodeConfig) {
|
||||||
|
c.pngCompressionLevel = level
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode writes the image img to w in the specified format (JPEG, PNG, GIF, TIFF or BMP).
|
||||||
|
func Encode(w io.Writer, img image.Image, format Format, opts ...EncodeOption) error {
|
||||||
|
cfg := defaultEncodeConfig
|
||||||
|
for _, option := range opts {
|
||||||
|
option(&cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch format {
|
||||||
|
case JPEG:
|
||||||
|
if nrgba, ok := img.(*image.NRGBA); ok && nrgba.Opaque() {
|
||||||
|
rgba := &image.RGBA{
|
||||||
|
Pix: nrgba.Pix,
|
||||||
|
Stride: nrgba.Stride,
|
||||||
|
Rect: nrgba.Rect,
|
||||||
|
}
|
||||||
|
return jpeg.Encode(w, rgba, &jpeg.Options{Quality: cfg.jpegQuality})
|
||||||
|
}
|
||||||
|
return jpeg.Encode(w, img, &jpeg.Options{Quality: cfg.jpegQuality})
|
||||||
|
|
||||||
|
case PNG:
|
||||||
|
encoder := png.Encoder{CompressionLevel: cfg.pngCompressionLevel}
|
||||||
|
return encoder.Encode(w, img)
|
||||||
|
|
||||||
|
case GIF:
|
||||||
|
return gif.Encode(w, img, &gif.Options{
|
||||||
|
NumColors: cfg.gifNumColors,
|
||||||
|
Quantizer: cfg.gifQuantizer,
|
||||||
|
Drawer: cfg.gifDrawer,
|
||||||
|
})
|
||||||
|
|
||||||
|
case TIFF:
|
||||||
|
return tiff.Encode(w, img, &tiff.Options{Compression: tiff.Deflate, Predictor: true})
|
||||||
|
|
||||||
|
case BMP:
|
||||||
|
return bmp.Encode(w, img)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrUnsupportedFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save saves the image to file with the specified filename.
|
||||||
|
// The format is determined from the filename extension:
|
||||||
|
// "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported.
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
//
|
||||||
|
// // Save the image as PNG.
|
||||||
|
// err := imaging.Save(img, "out.png")
|
||||||
|
//
|
||||||
|
// // Save the image as JPEG with optional quality parameter set to 80.
|
||||||
|
// err := imaging.Save(img, "out.jpg", imaging.JPEGQuality(80))
|
||||||
|
//
|
||||||
|
func Save(img image.Image, filename string, opts ...EncodeOption) (err error) {
|
||||||
|
f, err := FormatFromFilename(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
file, err := fs.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = Encode(file, img, f, opts...)
|
||||||
|
errc := file.Close()
|
||||||
|
if err == nil {
|
||||||
|
err = errc
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// orientation is an EXIF flag that specifies the transformation
|
||||||
|
// that should be applied to image to display it correctly.
|
||||||
|
type orientation int
|
||||||
|
|
||||||
|
const (
|
||||||
|
orientationUnspecified = 0
|
||||||
|
orientationNormal = 1
|
||||||
|
orientationFlipH = 2
|
||||||
|
orientationRotate180 = 3
|
||||||
|
orientationFlipV = 4
|
||||||
|
orientationTranspose = 5
|
||||||
|
orientationRotate270 = 6
|
||||||
|
orientationTransverse = 7
|
||||||
|
orientationRotate90 = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
// readOrientation tries to read the orientation EXIF flag from image data in r.
|
||||||
|
// If the EXIF data block is not found or the orientation flag is not found
|
||||||
|
// or any other error occures while reading the data, it returns the
|
||||||
|
// orientationUnspecified (0) value.
|
||||||
|
func readOrientation(r io.Reader) orientation {
|
||||||
|
const (
|
||||||
|
markerSOI = 0xffd8
|
||||||
|
markerAPP1 = 0xffe1
|
||||||
|
exifHeader = 0x45786966
|
||||||
|
byteOrderBE = 0x4d4d
|
||||||
|
byteOrderLE = 0x4949
|
||||||
|
orientationTag = 0x0112
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check if JPEG SOI marker is present.
|
||||||
|
var soi uint16
|
||||||
|
if err := binary.Read(r, binary.BigEndian, &soi); err != nil {
|
||||||
|
return orientationUnspecified
|
||||||
|
}
|
||||||
|
if soi != markerSOI {
|
||||||
|
return orientationUnspecified // Missing JPEG SOI marker.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find JPEG APP1 marker.
|
||||||
|
for {
|
||||||
|
var marker, size uint16
|
||||||
|
if err := binary.Read(r, binary.BigEndian, &marker); err != nil {
|
||||||
|
return orientationUnspecified
|
||||||
|
}
|
||||||
|
if err := binary.Read(r, binary.BigEndian, &size); err != nil {
|
||||||
|
return orientationUnspecified
|
||||||
|
}
|
||||||
|
if marker>>8 != 0xff {
|
||||||
|
return orientationUnspecified // Invalid JPEG marker.
|
||||||
|
}
|
||||||
|
if marker == markerAPP1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if size < 2 {
|
||||||
|
return orientationUnspecified // Invalid block size.
|
||||||
|
}
|
||||||
|
if _, err := io.CopyN(ioutil.Discard, r, int64(size-2)); err != nil {
|
||||||
|
return orientationUnspecified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if EXIF header is present.
|
||||||
|
var header uint32
|
||||||
|
if err := binary.Read(r, binary.BigEndian, &header); err != nil {
|
||||||
|
return orientationUnspecified
|
||||||
|
}
|
||||||
|
if header != exifHeader {
|
||||||
|
return orientationUnspecified
|
||||||
|
}
|
||||||
|
if _, err := io.CopyN(ioutil.Discard, r, 2); err != nil {
|
||||||
|
return orientationUnspecified
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read byte order information.
|
||||||
|
var (
|
||||||
|
byteOrderTag uint16
|
||||||
|
byteOrder binary.ByteOrder
|
||||||
|
)
|
||||||
|
if err := binary.Read(r, binary.BigEndian, &byteOrderTag); err != nil {
|
||||||
|
return orientationUnspecified
|
||||||
|
}
|
||||||
|
switch byteOrderTag {
|
||||||
|
case byteOrderBE:
|
||||||
|
byteOrder = binary.BigEndian
|
||||||
|
case byteOrderLE:
|
||||||
|
byteOrder = binary.LittleEndian
|
||||||
|
default:
|
||||||
|
return orientationUnspecified // Invalid byte order flag.
|
||||||
|
}
|
||||||
|
if _, err := io.CopyN(ioutil.Discard, r, 2); err != nil {
|
||||||
|
return orientationUnspecified
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the EXIF offset.
|
||||||
|
var offset uint32
|
||||||
|
if err := binary.Read(r, byteOrder, &offset); err != nil {
|
||||||
|
return orientationUnspecified
|
||||||
|
}
|
||||||
|
if offset < 8 {
|
||||||
|
return orientationUnspecified // Invalid offset value.
|
||||||
|
}
|
||||||
|
if _, err := io.CopyN(ioutil.Discard, r, int64(offset-8)); err != nil {
|
||||||
|
return orientationUnspecified
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the number of tags.
|
||||||
|
var numTags uint16
|
||||||
|
if err := binary.Read(r, byteOrder, &numTags); err != nil {
|
||||||
|
return orientationUnspecified
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the orientation tag.
|
||||||
|
for i := 0; i < int(numTags); i++ {
|
||||||
|
var tag uint16
|
||||||
|
if err := binary.Read(r, byteOrder, &tag); err != nil {
|
||||||
|
return orientationUnspecified
|
||||||
|
}
|
||||||
|
if tag != orientationTag {
|
||||||
|
if _, err := io.CopyN(ioutil.Discard, r, 10); err != nil {
|
||||||
|
return orientationUnspecified
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := io.CopyN(ioutil.Discard, r, 6); err != nil {
|
||||||
|
return orientationUnspecified
|
||||||
|
}
|
||||||
|
var val uint16
|
||||||
|
if err := binary.Read(r, byteOrder, &val); err != nil {
|
||||||
|
return orientationUnspecified
|
||||||
|
}
|
||||||
|
if val < 1 || val > 8 {
|
||||||
|
return orientationUnspecified // Invalid tag value.
|
||||||
|
}
|
||||||
|
return orientation(val)
|
||||||
|
}
|
||||||
|
return orientationUnspecified // Missing orientation tag.
|
||||||
|
}
|
||||||
|
|
||||||
|
// fixOrientation applies a transform to img corresponding to the given orientation flag.
|
||||||
|
func fixOrientation(img image.Image, o orientation) image.Image {
|
||||||
|
switch o {
|
||||||
|
case orientationNormal:
|
||||||
|
case orientationFlipH:
|
||||||
|
img = FlipH(img)
|
||||||
|
case orientationFlipV:
|
||||||
|
img = FlipV(img)
|
||||||
|
case orientationRotate90:
|
||||||
|
img = Rotate90(img)
|
||||||
|
case orientationRotate180:
|
||||||
|
img = Rotate180(img)
|
||||||
|
case orientationRotate270:
|
||||||
|
img = Rotate270(img)
|
||||||
|
case orientationTranspose:
|
||||||
|
img = Transpose(img)
|
||||||
|
case orientationTransverse:
|
||||||
|
img = Transverse(img)
|
||||||
|
}
|
||||||
|
return img
|
||||||
|
}
|
||||||
595
vendor/github.com/disintegration/imaging/resize.go
generated
vendored
Normal file
595
vendor/github.com/disintegration/imaging/resize.go
generated
vendored
Normal file
@@ -0,0 +1,595 @@
|
|||||||
|
package imaging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
type indexWeight struct {
|
||||||
|
index int
|
||||||
|
weight float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func precomputeWeights(dstSize, srcSize int, filter ResampleFilter) [][]indexWeight {
|
||||||
|
du := float64(srcSize) / float64(dstSize)
|
||||||
|
scale := du
|
||||||
|
if scale < 1.0 {
|
||||||
|
scale = 1.0
|
||||||
|
}
|
||||||
|
ru := math.Ceil(scale * filter.Support)
|
||||||
|
|
||||||
|
out := make([][]indexWeight, dstSize)
|
||||||
|
tmp := make([]indexWeight, 0, dstSize*int(ru+2)*2)
|
||||||
|
|
||||||
|
for v := 0; v < dstSize; v++ {
|
||||||
|
fu := (float64(v)+0.5)*du - 0.5
|
||||||
|
|
||||||
|
begin := int(math.Ceil(fu - ru))
|
||||||
|
if begin < 0 {
|
||||||
|
begin = 0
|
||||||
|
}
|
||||||
|
end := int(math.Floor(fu + ru))
|
||||||
|
if end > srcSize-1 {
|
||||||
|
end = srcSize - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
var sum float64
|
||||||
|
for u := begin; u <= end; u++ {
|
||||||
|
w := filter.Kernel((float64(u) - fu) / scale)
|
||||||
|
if w != 0 {
|
||||||
|
sum += w
|
||||||
|
tmp = append(tmp, indexWeight{index: u, weight: w})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sum != 0 {
|
||||||
|
for i := range tmp {
|
||||||
|
tmp[i].weight /= sum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out[v] = tmp
|
||||||
|
tmp = tmp[len(tmp):]
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize resizes the image to the specified width and height using the specified resampling
|
||||||
|
// filter and returns the transformed image. If one of width or height is 0, the image aspect
|
||||||
|
// ratio is preserved.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// dstImage := imaging.Resize(srcImage, 800, 600, imaging.Lanczos)
|
||||||
|
//
|
||||||
|
func Resize(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA {
|
||||||
|
dstW, dstH := width, height
|
||||||
|
if dstW < 0 || dstH < 0 {
|
||||||
|
return &image.NRGBA{}
|
||||||
|
}
|
||||||
|
if dstW == 0 && dstH == 0 {
|
||||||
|
return &image.NRGBA{}
|
||||||
|
}
|
||||||
|
|
||||||
|
srcW := img.Bounds().Dx()
|
||||||
|
srcH := img.Bounds().Dy()
|
||||||
|
if srcW <= 0 || srcH <= 0 {
|
||||||
|
return &image.NRGBA{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If new width or height is 0 then preserve aspect ratio, minimum 1px.
|
||||||
|
if dstW == 0 {
|
||||||
|
tmpW := float64(dstH) * float64(srcW) / float64(srcH)
|
||||||
|
dstW = int(math.Max(1.0, math.Floor(tmpW+0.5)))
|
||||||
|
}
|
||||||
|
if dstH == 0 {
|
||||||
|
tmpH := float64(dstW) * float64(srcH) / float64(srcW)
|
||||||
|
dstH = int(math.Max(1.0, math.Floor(tmpH+0.5)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter.Support <= 0 {
|
||||||
|
// Nearest-neighbor special case.
|
||||||
|
return resizeNearest(img, dstW, dstH)
|
||||||
|
}
|
||||||
|
|
||||||
|
if srcW != dstW && srcH != dstH {
|
||||||
|
return resizeVertical(resizeHorizontal(img, dstW, filter), dstH, filter)
|
||||||
|
}
|
||||||
|
if srcW != dstW {
|
||||||
|
return resizeHorizontal(img, dstW, filter)
|
||||||
|
}
|
||||||
|
if srcH != dstH {
|
||||||
|
return resizeVertical(img, dstH, filter)
|
||||||
|
}
|
||||||
|
return Clone(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resizeHorizontal(img image.Image, width int, filter ResampleFilter) *image.NRGBA {
|
||||||
|
src := newScanner(img)
|
||||||
|
dst := image.NewNRGBA(image.Rect(0, 0, width, src.h))
|
||||||
|
weights := precomputeWeights(width, src.w, filter)
|
||||||
|
parallel(0, src.h, func(ys <-chan int) {
|
||||||
|
scanLine := make([]uint8, src.w*4)
|
||||||
|
for y := range ys {
|
||||||
|
src.scan(0, y, src.w, y+1, scanLine)
|
||||||
|
j0 := y * dst.Stride
|
||||||
|
for x := range weights {
|
||||||
|
var r, g, b, a float64
|
||||||
|
for _, w := range weights[x] {
|
||||||
|
i := w.index * 4
|
||||||
|
s := scanLine[i : i+4 : i+4]
|
||||||
|
aw := float64(s[3]) * w.weight
|
||||||
|
r += float64(s[0]) * aw
|
||||||
|
g += float64(s[1]) * aw
|
||||||
|
b += float64(s[2]) * aw
|
||||||
|
a += aw
|
||||||
|
}
|
||||||
|
if a != 0 {
|
||||||
|
aInv := 1 / a
|
||||||
|
j := j0 + x*4
|
||||||
|
d := dst.Pix[j : j+4 : j+4]
|
||||||
|
d[0] = clamp(r * aInv)
|
||||||
|
d[1] = clamp(g * aInv)
|
||||||
|
d[2] = clamp(b * aInv)
|
||||||
|
d[3] = clamp(a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
func resizeVertical(img image.Image, height int, filter ResampleFilter) *image.NRGBA {
|
||||||
|
src := newScanner(img)
|
||||||
|
dst := image.NewNRGBA(image.Rect(0, 0, src.w, height))
|
||||||
|
weights := precomputeWeights(height, src.h, filter)
|
||||||
|
parallel(0, src.w, func(xs <-chan int) {
|
||||||
|
scanLine := make([]uint8, src.h*4)
|
||||||
|
for x := range xs {
|
||||||
|
src.scan(x, 0, x+1, src.h, scanLine)
|
||||||
|
for y := range weights {
|
||||||
|
var r, g, b, a float64
|
||||||
|
for _, w := range weights[y] {
|
||||||
|
i := w.index * 4
|
||||||
|
s := scanLine[i : i+4 : i+4]
|
||||||
|
aw := float64(s[3]) * w.weight
|
||||||
|
r += float64(s[0]) * aw
|
||||||
|
g += float64(s[1]) * aw
|
||||||
|
b += float64(s[2]) * aw
|
||||||
|
a += aw
|
||||||
|
}
|
||||||
|
if a != 0 {
|
||||||
|
aInv := 1 / a
|
||||||
|
j := y*dst.Stride + x*4
|
||||||
|
d := dst.Pix[j : j+4 : j+4]
|
||||||
|
d[0] = clamp(r * aInv)
|
||||||
|
d[1] = clamp(g * aInv)
|
||||||
|
d[2] = clamp(b * aInv)
|
||||||
|
d[3] = clamp(a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// resizeNearest is a fast nearest-neighbor resize, no filtering.
|
||||||
|
func resizeNearest(img image.Image, width, height int) *image.NRGBA {
|
||||||
|
dst := image.NewNRGBA(image.Rect(0, 0, width, height))
|
||||||
|
dx := float64(img.Bounds().Dx()) / float64(width)
|
||||||
|
dy := float64(img.Bounds().Dy()) / float64(height)
|
||||||
|
|
||||||
|
if dx > 1 && dy > 1 {
|
||||||
|
src := newScanner(img)
|
||||||
|
parallel(0, height, func(ys <-chan int) {
|
||||||
|
for y := range ys {
|
||||||
|
srcY := int((float64(y) + 0.5) * dy)
|
||||||
|
dstOff := y * dst.Stride
|
||||||
|
for x := 0; x < width; x++ {
|
||||||
|
srcX := int((float64(x) + 0.5) * dx)
|
||||||
|
src.scan(srcX, srcY, srcX+1, srcY+1, dst.Pix[dstOff:dstOff+4])
|
||||||
|
dstOff += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
src := toNRGBA(img)
|
||||||
|
parallel(0, height, func(ys <-chan int) {
|
||||||
|
for y := range ys {
|
||||||
|
srcY := int((float64(y) + 0.5) * dy)
|
||||||
|
srcOff0 := srcY * src.Stride
|
||||||
|
dstOff := y * dst.Stride
|
||||||
|
for x := 0; x < width; x++ {
|
||||||
|
srcX := int((float64(x) + 0.5) * dx)
|
||||||
|
srcOff := srcOff0 + srcX*4
|
||||||
|
copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4])
|
||||||
|
dstOff += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fit scales down the image using the specified resample filter to fit the specified
|
||||||
|
// maximum width and height and returns the transformed image.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// dstImage := imaging.Fit(srcImage, 800, 600, imaging.Lanczos)
|
||||||
|
//
|
||||||
|
func Fit(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA {
|
||||||
|
maxW, maxH := width, height
|
||||||
|
|
||||||
|
if maxW <= 0 || maxH <= 0 {
|
||||||
|
return &image.NRGBA{}
|
||||||
|
}
|
||||||
|
|
||||||
|
srcBounds := img.Bounds()
|
||||||
|
srcW := srcBounds.Dx()
|
||||||
|
srcH := srcBounds.Dy()
|
||||||
|
|
||||||
|
if srcW <= 0 || srcH <= 0 {
|
||||||
|
return &image.NRGBA{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if srcW <= maxW && srcH <= maxH {
|
||||||
|
return Clone(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
srcAspectRatio := float64(srcW) / float64(srcH)
|
||||||
|
maxAspectRatio := float64(maxW) / float64(maxH)
|
||||||
|
|
||||||
|
var newW, newH int
|
||||||
|
if srcAspectRatio > maxAspectRatio {
|
||||||
|
newW = maxW
|
||||||
|
newH = int(float64(newW) / srcAspectRatio)
|
||||||
|
} else {
|
||||||
|
newH = maxH
|
||||||
|
newW = int(float64(newH) * srcAspectRatio)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Resize(img, newW, newH, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill creates an image with the specified dimensions and fills it with the scaled source image.
|
||||||
|
// To achieve the correct aspect ratio without stretching, the source image will be cropped.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// dstImage := imaging.Fill(srcImage, 800, 600, imaging.Center, imaging.Lanczos)
|
||||||
|
//
|
||||||
|
func Fill(img image.Image, width, height int, anchor Anchor, filter ResampleFilter) *image.NRGBA {
|
||||||
|
dstW, dstH := width, height
|
||||||
|
|
||||||
|
if dstW <= 0 || dstH <= 0 {
|
||||||
|
return &image.NRGBA{}
|
||||||
|
}
|
||||||
|
|
||||||
|
srcBounds := img.Bounds()
|
||||||
|
srcW := srcBounds.Dx()
|
||||||
|
srcH := srcBounds.Dy()
|
||||||
|
|
||||||
|
if srcW <= 0 || srcH <= 0 {
|
||||||
|
return &image.NRGBA{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if srcW == dstW && srcH == dstH {
|
||||||
|
return Clone(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
if srcW >= 100 && srcH >= 100 {
|
||||||
|
return cropAndResize(img, dstW, dstH, anchor, filter)
|
||||||
|
}
|
||||||
|
return resizeAndCrop(img, dstW, dstH, anchor, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cropAndResize crops the image to the smallest possible size that has the required aspect ratio using
|
||||||
|
// the given anchor point, then scales it to the specified dimensions and returns the transformed image.
|
||||||
|
//
|
||||||
|
// This is generally faster than resizing first, but may result in inaccuracies when used on small source images.
|
||||||
|
func cropAndResize(img image.Image, width, height int, anchor Anchor, filter ResampleFilter) *image.NRGBA {
|
||||||
|
dstW, dstH := width, height
|
||||||
|
|
||||||
|
srcBounds := img.Bounds()
|
||||||
|
srcW := srcBounds.Dx()
|
||||||
|
srcH := srcBounds.Dy()
|
||||||
|
srcAspectRatio := float64(srcW) / float64(srcH)
|
||||||
|
dstAspectRatio := float64(dstW) / float64(dstH)
|
||||||
|
|
||||||
|
var tmp *image.NRGBA
|
||||||
|
if srcAspectRatio < dstAspectRatio {
|
||||||
|
cropH := float64(srcW) * float64(dstH) / float64(dstW)
|
||||||
|
tmp = CropAnchor(img, srcW, int(math.Max(1, cropH)+0.5), anchor)
|
||||||
|
} else {
|
||||||
|
cropW := float64(srcH) * float64(dstW) / float64(dstH)
|
||||||
|
tmp = CropAnchor(img, int(math.Max(1, cropW)+0.5), srcH, anchor)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Resize(tmp, dstW, dstH, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// resizeAndCrop resizes the image to the smallest possible size that will cover the specified dimensions,
|
||||||
|
// crops the resized image to the specified dimensions using the given anchor point and returns
|
||||||
|
// the transformed image.
|
||||||
|
func resizeAndCrop(img image.Image, width, height int, anchor Anchor, filter ResampleFilter) *image.NRGBA {
|
||||||
|
dstW, dstH := width, height
|
||||||
|
|
||||||
|
srcBounds := img.Bounds()
|
||||||
|
srcW := srcBounds.Dx()
|
||||||
|
srcH := srcBounds.Dy()
|
||||||
|
srcAspectRatio := float64(srcW) / float64(srcH)
|
||||||
|
dstAspectRatio := float64(dstW) / float64(dstH)
|
||||||
|
|
||||||
|
var tmp *image.NRGBA
|
||||||
|
if srcAspectRatio < dstAspectRatio {
|
||||||
|
tmp = Resize(img, dstW, 0, filter)
|
||||||
|
} else {
|
||||||
|
tmp = Resize(img, 0, dstH, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
return CropAnchor(tmp, dstW, dstH, anchor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thumbnail scales the image up or down using the specified resample filter, crops it
|
||||||
|
// to the specified width and hight and returns the transformed image.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// dstImage := imaging.Thumbnail(srcImage, 100, 100, imaging.Lanczos)
|
||||||
|
//
|
||||||
|
func Thumbnail(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA {
|
||||||
|
return Fill(img, width, height, Center, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResampleFilter specifies a resampling filter to be used for image resizing.
|
||||||
|
//
|
||||||
|
// General filter recommendations:
|
||||||
|
//
|
||||||
|
// - Lanczos
|
||||||
|
// A high-quality resampling filter for photographic images yielding sharp results.
|
||||||
|
//
|
||||||
|
// - CatmullRom
|
||||||
|
// A sharp cubic filter that is faster than Lanczos filter while providing similar results.
|
||||||
|
//
|
||||||
|
// - MitchellNetravali
|
||||||
|
// A cubic filter that produces smoother results with less ringing artifacts than CatmullRom.
|
||||||
|
//
|
||||||
|
// - Linear
|
||||||
|
// Bilinear resampling filter, produces a smooth output. Faster than cubic filters.
|
||||||
|
//
|
||||||
|
// - Box
|
||||||
|
// Simple and fast averaging filter appropriate for downscaling.
|
||||||
|
// When upscaling it's similar to NearestNeighbor.
|
||||||
|
//
|
||||||
|
// - NearestNeighbor
|
||||||
|
// Fastest resampling filter, no antialiasing.
|
||||||
|
//
|
||||||
|
type ResampleFilter struct {
|
||||||
|
Support float64
|
||||||
|
Kernel func(float64) float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NearestNeighbor is a nearest-neighbor filter (no anti-aliasing).
|
||||||
|
var NearestNeighbor ResampleFilter
|
||||||
|
|
||||||
|
// Box filter (averaging pixels).
|
||||||
|
var Box ResampleFilter
|
||||||
|
|
||||||
|
// Linear filter.
|
||||||
|
var Linear ResampleFilter
|
||||||
|
|
||||||
|
// Hermite cubic spline filter (BC-spline; B=0; C=0).
|
||||||
|
var Hermite ResampleFilter
|
||||||
|
|
||||||
|
// MitchellNetravali is Mitchell-Netravali cubic filter (BC-spline; B=1/3; C=1/3).
|
||||||
|
var MitchellNetravali ResampleFilter
|
||||||
|
|
||||||
|
// CatmullRom is a Catmull-Rom - sharp cubic filter (BC-spline; B=0; C=0.5).
|
||||||
|
var CatmullRom ResampleFilter
|
||||||
|
|
||||||
|
// BSpline is a smooth cubic filter (BC-spline; B=1; C=0).
|
||||||
|
var BSpline ResampleFilter
|
||||||
|
|
||||||
|
// Gaussian is a Gaussian blurring filter.
|
||||||
|
var Gaussian ResampleFilter
|
||||||
|
|
||||||
|
// Bartlett is a Bartlett-windowed sinc filter (3 lobes).
|
||||||
|
var Bartlett ResampleFilter
|
||||||
|
|
||||||
|
// Lanczos filter (3 lobes).
|
||||||
|
var Lanczos ResampleFilter
|
||||||
|
|
||||||
|
// Hann is a Hann-windowed sinc filter (3 lobes).
|
||||||
|
var Hann ResampleFilter
|
||||||
|
|
||||||
|
// Hamming is a Hamming-windowed sinc filter (3 lobes).
|
||||||
|
var Hamming ResampleFilter
|
||||||
|
|
||||||
|
// Blackman is a Blackman-windowed sinc filter (3 lobes).
|
||||||
|
var Blackman ResampleFilter
|
||||||
|
|
||||||
|
// Welch is a Welch-windowed sinc filter (parabolic window, 3 lobes).
|
||||||
|
var Welch ResampleFilter
|
||||||
|
|
||||||
|
// Cosine is a Cosine-windowed sinc filter (3 lobes).
|
||||||
|
var Cosine ResampleFilter
|
||||||
|
|
||||||
|
func bcspline(x, b, c float64) float64 {
|
||||||
|
var y float64
|
||||||
|
x = math.Abs(x)
|
||||||
|
if x < 1.0 {
|
||||||
|
y = ((12-9*b-6*c)*x*x*x + (-18+12*b+6*c)*x*x + (6 - 2*b)) / 6
|
||||||
|
} else if x < 2.0 {
|
||||||
|
y = ((-b-6*c)*x*x*x + (6*b+30*c)*x*x + (-12*b-48*c)*x + (8*b + 24*c)) / 6
|
||||||
|
}
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
|
||||||
|
func sinc(x float64) float64 {
|
||||||
|
if x == 0 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return math.Sin(math.Pi*x) / (math.Pi * x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
NearestNeighbor = ResampleFilter{
|
||||||
|
Support: 0.0, // special case - not applying the filter
|
||||||
|
}
|
||||||
|
|
||||||
|
Box = ResampleFilter{
|
||||||
|
Support: 0.5,
|
||||||
|
Kernel: func(x float64) float64 {
|
||||||
|
x = math.Abs(x)
|
||||||
|
if x <= 0.5 {
|
||||||
|
return 1.0
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Linear = ResampleFilter{
|
||||||
|
Support: 1.0,
|
||||||
|
Kernel: func(x float64) float64 {
|
||||||
|
x = math.Abs(x)
|
||||||
|
if x < 1.0 {
|
||||||
|
return 1.0 - x
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Hermite = ResampleFilter{
|
||||||
|
Support: 1.0,
|
||||||
|
Kernel: func(x float64) float64 {
|
||||||
|
x = math.Abs(x)
|
||||||
|
if x < 1.0 {
|
||||||
|
return bcspline(x, 0.0, 0.0)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
MitchellNetravali = ResampleFilter{
|
||||||
|
Support: 2.0,
|
||||||
|
Kernel: func(x float64) float64 {
|
||||||
|
x = math.Abs(x)
|
||||||
|
if x < 2.0 {
|
||||||
|
return bcspline(x, 1.0/3.0, 1.0/3.0)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
CatmullRom = ResampleFilter{
|
||||||
|
Support: 2.0,
|
||||||
|
Kernel: func(x float64) float64 {
|
||||||
|
x = math.Abs(x)
|
||||||
|
if x < 2.0 {
|
||||||
|
return bcspline(x, 0.0, 0.5)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
BSpline = ResampleFilter{
|
||||||
|
Support: 2.0,
|
||||||
|
Kernel: func(x float64) float64 {
|
||||||
|
x = math.Abs(x)
|
||||||
|
if x < 2.0 {
|
||||||
|
return bcspline(x, 1.0, 0.0)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Gaussian = ResampleFilter{
|
||||||
|
Support: 2.0,
|
||||||
|
Kernel: func(x float64) float64 {
|
||||||
|
x = math.Abs(x)
|
||||||
|
if x < 2.0 {
|
||||||
|
return math.Exp(-2 * x * x)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Bartlett = ResampleFilter{
|
||||||
|
Support: 3.0,
|
||||||
|
Kernel: func(x float64) float64 {
|
||||||
|
x = math.Abs(x)
|
||||||
|
if x < 3.0 {
|
||||||
|
return sinc(x) * (3.0 - x) / 3.0
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Lanczos = ResampleFilter{
|
||||||
|
Support: 3.0,
|
||||||
|
Kernel: func(x float64) float64 {
|
||||||
|
x = math.Abs(x)
|
||||||
|
if x < 3.0 {
|
||||||
|
return sinc(x) * sinc(x/3.0)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Hann = ResampleFilter{
|
||||||
|
Support: 3.0,
|
||||||
|
Kernel: func(x float64) float64 {
|
||||||
|
x = math.Abs(x)
|
||||||
|
if x < 3.0 {
|
||||||
|
return sinc(x) * (0.5 + 0.5*math.Cos(math.Pi*x/3.0))
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Hamming = ResampleFilter{
|
||||||
|
Support: 3.0,
|
||||||
|
Kernel: func(x float64) float64 {
|
||||||
|
x = math.Abs(x)
|
||||||
|
if x < 3.0 {
|
||||||
|
return sinc(x) * (0.54 + 0.46*math.Cos(math.Pi*x/3.0))
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Blackman = ResampleFilter{
|
||||||
|
Support: 3.0,
|
||||||
|
Kernel: func(x float64) float64 {
|
||||||
|
x = math.Abs(x)
|
||||||
|
if x < 3.0 {
|
||||||
|
return sinc(x) * (0.42 - 0.5*math.Cos(math.Pi*x/3.0+math.Pi) + 0.08*math.Cos(2.0*math.Pi*x/3.0))
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Welch = ResampleFilter{
|
||||||
|
Support: 3.0,
|
||||||
|
Kernel: func(x float64) float64 {
|
||||||
|
x = math.Abs(x)
|
||||||
|
if x < 3.0 {
|
||||||
|
return sinc(x) * (1.0 - (x * x / 9.0))
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Cosine = ResampleFilter{
|
||||||
|
Support: 3.0,
|
||||||
|
Kernel: func(x float64) float64 {
|
||||||
|
x = math.Abs(x)
|
||||||
|
if x < 3.0 {
|
||||||
|
return sinc(x) * math.Cos((math.Pi/2.0)*(x/3.0))
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
285
vendor/github.com/disintegration/imaging/scanner.go
generated
vendored
Normal file
285
vendor/github.com/disintegration/imaging/scanner.go
generated
vendored
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
package imaging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
type scanner struct {
|
||||||
|
image image.Image
|
||||||
|
w, h int
|
||||||
|
palette []color.NRGBA
|
||||||
|
}
|
||||||
|
|
||||||
|
func newScanner(img image.Image) *scanner {
|
||||||
|
s := &scanner{
|
||||||
|
image: img,
|
||||||
|
w: img.Bounds().Dx(),
|
||||||
|
h: img.Bounds().Dy(),
|
||||||
|
}
|
||||||
|
if img, ok := img.(*image.Paletted); ok {
|
||||||
|
s.palette = make([]color.NRGBA, len(img.Palette))
|
||||||
|
for i := 0; i < len(img.Palette); i++ {
|
||||||
|
s.palette[i] = color.NRGBAModel.Convert(img.Palette[i]).(color.NRGBA)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// scan scans the given rectangular region of the image into dst.
|
||||||
|
func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) {
|
||||||
|
switch img := s.image.(type) {
|
||||||
|
case *image.NRGBA:
|
||||||
|
size := (x2 - x1) * 4
|
||||||
|
j := 0
|
||||||
|
i := y1*img.Stride + x1*4
|
||||||
|
if size == 4 {
|
||||||
|
for y := y1; y < y2; y++ {
|
||||||
|
d := dst[j : j+4 : j+4]
|
||||||
|
s := img.Pix[i : i+4 : i+4]
|
||||||
|
d[0] = s[0]
|
||||||
|
d[1] = s[1]
|
||||||
|
d[2] = s[2]
|
||||||
|
d[3] = s[3]
|
||||||
|
j += size
|
||||||
|
i += img.Stride
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for y := y1; y < y2; y++ {
|
||||||
|
copy(dst[j:j+size], img.Pix[i:i+size])
|
||||||
|
j += size
|
||||||
|
i += img.Stride
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case *image.NRGBA64:
|
||||||
|
j := 0
|
||||||
|
for y := y1; y < y2; y++ {
|
||||||
|
i := y*img.Stride + x1*8
|
||||||
|
for x := x1; x < x2; x++ {
|
||||||
|
s := img.Pix[i : i+8 : i+8]
|
||||||
|
d := dst[j : j+4 : j+4]
|
||||||
|
d[0] = s[0]
|
||||||
|
d[1] = s[2]
|
||||||
|
d[2] = s[4]
|
||||||
|
d[3] = s[6]
|
||||||
|
j += 4
|
||||||
|
i += 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case *image.RGBA:
|
||||||
|
j := 0
|
||||||
|
for y := y1; y < y2; y++ {
|
||||||
|
i := y*img.Stride + x1*4
|
||||||
|
for x := x1; x < x2; x++ {
|
||||||
|
d := dst[j : j+4 : j+4]
|
||||||
|
a := img.Pix[i+3]
|
||||||
|
switch a {
|
||||||
|
case 0:
|
||||||
|
d[0] = 0
|
||||||
|
d[1] = 0
|
||||||
|
d[2] = 0
|
||||||
|
d[3] = a
|
||||||
|
case 0xff:
|
||||||
|
s := img.Pix[i : i+4 : i+4]
|
||||||
|
d[0] = s[0]
|
||||||
|
d[1] = s[1]
|
||||||
|
d[2] = s[2]
|
||||||
|
d[3] = a
|
||||||
|
default:
|
||||||
|
s := img.Pix[i : i+4 : i+4]
|
||||||
|
r16 := uint16(s[0])
|
||||||
|
g16 := uint16(s[1])
|
||||||
|
b16 := uint16(s[2])
|
||||||
|
a16 := uint16(a)
|
||||||
|
d[0] = uint8(r16 * 0xff / a16)
|
||||||
|
d[1] = uint8(g16 * 0xff / a16)
|
||||||
|
d[2] = uint8(b16 * 0xff / a16)
|
||||||
|
d[3] = a
|
||||||
|
}
|
||||||
|
j += 4
|
||||||
|
i += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case *image.RGBA64:
|
||||||
|
j := 0
|
||||||
|
for y := y1; y < y2; y++ {
|
||||||
|
i := y*img.Stride + x1*8
|
||||||
|
for x := x1; x < x2; x++ {
|
||||||
|
s := img.Pix[i : i+8 : i+8]
|
||||||
|
d := dst[j : j+4 : j+4]
|
||||||
|
a := s[6]
|
||||||
|
switch a {
|
||||||
|
case 0:
|
||||||
|
d[0] = 0
|
||||||
|
d[1] = 0
|
||||||
|
d[2] = 0
|
||||||
|
case 0xff:
|
||||||
|
d[0] = s[0]
|
||||||
|
d[1] = s[2]
|
||||||
|
d[2] = s[4]
|
||||||
|
default:
|
||||||
|
r32 := uint32(s[0])<<8 | uint32(s[1])
|
||||||
|
g32 := uint32(s[2])<<8 | uint32(s[3])
|
||||||
|
b32 := uint32(s[4])<<8 | uint32(s[5])
|
||||||
|
a32 := uint32(s[6])<<8 | uint32(s[7])
|
||||||
|
d[0] = uint8((r32 * 0xffff / a32) >> 8)
|
||||||
|
d[1] = uint8((g32 * 0xffff / a32) >> 8)
|
||||||
|
d[2] = uint8((b32 * 0xffff / a32) >> 8)
|
||||||
|
}
|
||||||
|
d[3] = a
|
||||||
|
j += 4
|
||||||
|
i += 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case *image.Gray:
|
||||||
|
j := 0
|
||||||
|
for y := y1; y < y2; y++ {
|
||||||
|
i := y*img.Stride + x1
|
||||||
|
for x := x1; x < x2; x++ {
|
||||||
|
c := img.Pix[i]
|
||||||
|
d := dst[j : j+4 : j+4]
|
||||||
|
d[0] = c
|
||||||
|
d[1] = c
|
||||||
|
d[2] = c
|
||||||
|
d[3] = 0xff
|
||||||
|
j += 4
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case *image.Gray16:
|
||||||
|
j := 0
|
||||||
|
for y := y1; y < y2; y++ {
|
||||||
|
i := y*img.Stride + x1*2
|
||||||
|
for x := x1; x < x2; x++ {
|
||||||
|
c := img.Pix[i]
|
||||||
|
d := dst[j : j+4 : j+4]
|
||||||
|
d[0] = c
|
||||||
|
d[1] = c
|
||||||
|
d[2] = c
|
||||||
|
d[3] = 0xff
|
||||||
|
j += 4
|
||||||
|
i += 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case *image.YCbCr:
|
||||||
|
j := 0
|
||||||
|
x1 += img.Rect.Min.X
|
||||||
|
x2 += img.Rect.Min.X
|
||||||
|
y1 += img.Rect.Min.Y
|
||||||
|
y2 += img.Rect.Min.Y
|
||||||
|
|
||||||
|
hy := img.Rect.Min.Y / 2
|
||||||
|
hx := img.Rect.Min.X / 2
|
||||||
|
for y := y1; y < y2; y++ {
|
||||||
|
iy := (y-img.Rect.Min.Y)*img.YStride + (x1 - img.Rect.Min.X)
|
||||||
|
|
||||||
|
var yBase int
|
||||||
|
switch img.SubsampleRatio {
|
||||||
|
case image.YCbCrSubsampleRatio444, image.YCbCrSubsampleRatio422:
|
||||||
|
yBase = (y - img.Rect.Min.Y) * img.CStride
|
||||||
|
case image.YCbCrSubsampleRatio420, image.YCbCrSubsampleRatio440:
|
||||||
|
yBase = (y/2 - hy) * img.CStride
|
||||||
|
}
|
||||||
|
|
||||||
|
for x := x1; x < x2; x++ {
|
||||||
|
var ic int
|
||||||
|
switch img.SubsampleRatio {
|
||||||
|
case image.YCbCrSubsampleRatio444, image.YCbCrSubsampleRatio440:
|
||||||
|
ic = yBase + (x - img.Rect.Min.X)
|
||||||
|
case image.YCbCrSubsampleRatio422, image.YCbCrSubsampleRatio420:
|
||||||
|
ic = yBase + (x/2 - hx)
|
||||||
|
default:
|
||||||
|
ic = img.COffset(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
yy1 := int32(img.Y[iy]) * 0x10101
|
||||||
|
cb1 := int32(img.Cb[ic]) - 128
|
||||||
|
cr1 := int32(img.Cr[ic]) - 128
|
||||||
|
|
||||||
|
r := yy1 + 91881*cr1
|
||||||
|
if uint32(r)&0xff000000 == 0 {
|
||||||
|
r >>= 16
|
||||||
|
} else {
|
||||||
|
r = ^(r >> 31)
|
||||||
|
}
|
||||||
|
|
||||||
|
g := yy1 - 22554*cb1 - 46802*cr1
|
||||||
|
if uint32(g)&0xff000000 == 0 {
|
||||||
|
g >>= 16
|
||||||
|
} else {
|
||||||
|
g = ^(g >> 31)
|
||||||
|
}
|
||||||
|
|
||||||
|
b := yy1 + 116130*cb1
|
||||||
|
if uint32(b)&0xff000000 == 0 {
|
||||||
|
b >>= 16
|
||||||
|
} else {
|
||||||
|
b = ^(b >> 31)
|
||||||
|
}
|
||||||
|
|
||||||
|
d := dst[j : j+4 : j+4]
|
||||||
|
d[0] = uint8(r)
|
||||||
|
d[1] = uint8(g)
|
||||||
|
d[2] = uint8(b)
|
||||||
|
d[3] = 0xff
|
||||||
|
|
||||||
|
iy++
|
||||||
|
j += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case *image.Paletted:
|
||||||
|
j := 0
|
||||||
|
for y := y1; y < y2; y++ {
|
||||||
|
i := y*img.Stride + x1
|
||||||
|
for x := x1; x < x2; x++ {
|
||||||
|
c := s.palette[img.Pix[i]]
|
||||||
|
d := dst[j : j+4 : j+4]
|
||||||
|
d[0] = c.R
|
||||||
|
d[1] = c.G
|
||||||
|
d[2] = c.B
|
||||||
|
d[3] = c.A
|
||||||
|
j += 4
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
j := 0
|
||||||
|
b := s.image.Bounds()
|
||||||
|
x1 += b.Min.X
|
||||||
|
x2 += b.Min.X
|
||||||
|
y1 += b.Min.Y
|
||||||
|
y2 += b.Min.Y
|
||||||
|
for y := y1; y < y2; y++ {
|
||||||
|
for x := x1; x < x2; x++ {
|
||||||
|
r16, g16, b16, a16 := s.image.At(x, y).RGBA()
|
||||||
|
d := dst[j : j+4 : j+4]
|
||||||
|
switch a16 {
|
||||||
|
case 0xffff:
|
||||||
|
d[0] = uint8(r16 >> 8)
|
||||||
|
d[1] = uint8(g16 >> 8)
|
||||||
|
d[2] = uint8(b16 >> 8)
|
||||||
|
d[3] = 0xff
|
||||||
|
case 0:
|
||||||
|
d[0] = 0
|
||||||
|
d[1] = 0
|
||||||
|
d[2] = 0
|
||||||
|
d[3] = 0
|
||||||
|
default:
|
||||||
|
d[0] = uint8(((r16 * 0xffff) / a16) >> 8)
|
||||||
|
d[1] = uint8(((g16 * 0xffff) / a16) >> 8)
|
||||||
|
d[2] = uint8(((b16 * 0xffff) / a16) >> 8)
|
||||||
|
d[3] = uint8(a16 >> 8)
|
||||||
|
}
|
||||||
|
j += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
249
vendor/github.com/disintegration/imaging/tools.go
generated
vendored
Normal file
249
vendor/github.com/disintegration/imaging/tools.go
generated
vendored
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
package imaging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New creates a new image with the specified width and height, and fills it with the specified color.
|
||||||
|
func New(width, height int, fillColor color.Color) *image.NRGBA {
|
||||||
|
if width <= 0 || height <= 0 {
|
||||||
|
return &image.NRGBA{}
|
||||||
|
}
|
||||||
|
|
||||||
|
c := color.NRGBAModel.Convert(fillColor).(color.NRGBA)
|
||||||
|
if (c == color.NRGBA{0, 0, 0, 0}) {
|
||||||
|
return image.NewNRGBA(image.Rect(0, 0, width, height))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &image.NRGBA{
|
||||||
|
Pix: bytes.Repeat([]byte{c.R, c.G, c.B, c.A}, width*height),
|
||||||
|
Stride: 4 * width,
|
||||||
|
Rect: image.Rect(0, 0, width, height),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone returns a copy of the given image.
|
||||||
|
func Clone(img image.Image) *image.NRGBA {
|
||||||
|
src := newScanner(img)
|
||||||
|
dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
|
||||||
|
size := src.w * 4
|
||||||
|
parallel(0, src.h, func(ys <-chan int) {
|
||||||
|
for y := range ys {
|
||||||
|
i := y * dst.Stride
|
||||||
|
src.scan(0, y, src.w, y+1, dst.Pix[i:i+size])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anchor is the anchor point for image alignment.
|
||||||
|
type Anchor int
|
||||||
|
|
||||||
|
// Anchor point positions.
|
||||||
|
const (
|
||||||
|
Center Anchor = iota
|
||||||
|
TopLeft
|
||||||
|
Top
|
||||||
|
TopRight
|
||||||
|
Left
|
||||||
|
Right
|
||||||
|
BottomLeft
|
||||||
|
Bottom
|
||||||
|
BottomRight
|
||||||
|
)
|
||||||
|
|
||||||
|
func anchorPt(b image.Rectangle, w, h int, anchor Anchor) image.Point {
|
||||||
|
var x, y int
|
||||||
|
switch anchor {
|
||||||
|
case TopLeft:
|
||||||
|
x = b.Min.X
|
||||||
|
y = b.Min.Y
|
||||||
|
case Top:
|
||||||
|
x = b.Min.X + (b.Dx()-w)/2
|
||||||
|
y = b.Min.Y
|
||||||
|
case TopRight:
|
||||||
|
x = b.Max.X - w
|
||||||
|
y = b.Min.Y
|
||||||
|
case Left:
|
||||||
|
x = b.Min.X
|
||||||
|
y = b.Min.Y + (b.Dy()-h)/2
|
||||||
|
case Right:
|
||||||
|
x = b.Max.X - w
|
||||||
|
y = b.Min.Y + (b.Dy()-h)/2
|
||||||
|
case BottomLeft:
|
||||||
|
x = b.Min.X
|
||||||
|
y = b.Max.Y - h
|
||||||
|
case Bottom:
|
||||||
|
x = b.Min.X + (b.Dx()-w)/2
|
||||||
|
y = b.Max.Y - h
|
||||||
|
case BottomRight:
|
||||||
|
x = b.Max.X - w
|
||||||
|
y = b.Max.Y - h
|
||||||
|
default:
|
||||||
|
x = b.Min.X + (b.Dx()-w)/2
|
||||||
|
y = b.Min.Y + (b.Dy()-h)/2
|
||||||
|
}
|
||||||
|
return image.Pt(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crop cuts out a rectangular region with the specified bounds
|
||||||
|
// from the image and returns the cropped image.
|
||||||
|
func Crop(img image.Image, rect image.Rectangle) *image.NRGBA {
|
||||||
|
r := rect.Intersect(img.Bounds()).Sub(img.Bounds().Min)
|
||||||
|
if r.Empty() {
|
||||||
|
return &image.NRGBA{}
|
||||||
|
}
|
||||||
|
src := newScanner(img)
|
||||||
|
dst := image.NewNRGBA(image.Rect(0, 0, r.Dx(), r.Dy()))
|
||||||
|
rowSize := r.Dx() * 4
|
||||||
|
parallel(r.Min.Y, r.Max.Y, func(ys <-chan int) {
|
||||||
|
for y := range ys {
|
||||||
|
i := (y - r.Min.Y) * dst.Stride
|
||||||
|
src.scan(r.Min.X, y, r.Max.X, y+1, dst.Pix[i:i+rowSize])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// CropAnchor cuts out a rectangular region with the specified size
|
||||||
|
// from the image using the specified anchor point and returns the cropped image.
|
||||||
|
func CropAnchor(img image.Image, width, height int, anchor Anchor) *image.NRGBA {
|
||||||
|
srcBounds := img.Bounds()
|
||||||
|
pt := anchorPt(srcBounds, width, height, anchor)
|
||||||
|
r := image.Rect(0, 0, width, height).Add(pt)
|
||||||
|
b := srcBounds.Intersect(r)
|
||||||
|
return Crop(img, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CropCenter cuts out a rectangular region with the specified size
|
||||||
|
// from the center of the image and returns the cropped image.
|
||||||
|
func CropCenter(img image.Image, width, height int) *image.NRGBA {
|
||||||
|
return CropAnchor(img, width, height, Center)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paste pastes the img image to the background image at the specified position and returns the combined image.
|
||||||
|
func Paste(background, img image.Image, pos image.Point) *image.NRGBA {
|
||||||
|
dst := Clone(background)
|
||||||
|
pos = pos.Sub(background.Bounds().Min)
|
||||||
|
pasteRect := image.Rectangle{Min: pos, Max: pos.Add(img.Bounds().Size())}
|
||||||
|
interRect := pasteRect.Intersect(dst.Bounds())
|
||||||
|
if interRect.Empty() {
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
src := newScanner(img)
|
||||||
|
parallel(interRect.Min.Y, interRect.Max.Y, func(ys <-chan int) {
|
||||||
|
for y := range ys {
|
||||||
|
x1 := interRect.Min.X - pasteRect.Min.X
|
||||||
|
x2 := interRect.Max.X - pasteRect.Min.X
|
||||||
|
y1 := y - pasteRect.Min.Y
|
||||||
|
y2 := y1 + 1
|
||||||
|
i1 := y*dst.Stride + interRect.Min.X*4
|
||||||
|
i2 := i1 + interRect.Dx()*4
|
||||||
|
src.scan(x1, y1, x2, y2, dst.Pix[i1:i2])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// PasteCenter pastes the img image to the center of the background image and returns the combined image.
|
||||||
|
func PasteCenter(background, img image.Image) *image.NRGBA {
|
||||||
|
bgBounds := background.Bounds()
|
||||||
|
bgW := bgBounds.Dx()
|
||||||
|
bgH := bgBounds.Dy()
|
||||||
|
bgMinX := bgBounds.Min.X
|
||||||
|
bgMinY := bgBounds.Min.Y
|
||||||
|
|
||||||
|
centerX := bgMinX + bgW/2
|
||||||
|
centerY := bgMinY + bgH/2
|
||||||
|
|
||||||
|
x0 := centerX - img.Bounds().Dx()/2
|
||||||
|
y0 := centerY - img.Bounds().Dy()/2
|
||||||
|
|
||||||
|
return Paste(background, img, image.Pt(x0, y0))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overlay draws the img image over the background image at given position
|
||||||
|
// and returns the combined image. Opacity parameter is the opacity of the img
|
||||||
|
// image layer, used to compose the images, it must be from 0.0 to 1.0.
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
//
|
||||||
|
// // Draw spriteImage over backgroundImage at the given position (x=50, y=50).
|
||||||
|
// dstImage := imaging.Overlay(backgroundImage, spriteImage, image.Pt(50, 50), 1.0)
|
||||||
|
//
|
||||||
|
// // Blend two opaque images of the same size.
|
||||||
|
// dstImage := imaging.Overlay(imageOne, imageTwo, image.Pt(0, 0), 0.5)
|
||||||
|
//
|
||||||
|
func Overlay(background, img image.Image, pos image.Point, opacity float64) *image.NRGBA {
|
||||||
|
opacity = math.Min(math.Max(opacity, 0.0), 1.0) // Ensure 0.0 <= opacity <= 1.0.
|
||||||
|
dst := Clone(background)
|
||||||
|
pos = pos.Sub(background.Bounds().Min)
|
||||||
|
pasteRect := image.Rectangle{Min: pos, Max: pos.Add(img.Bounds().Size())}
|
||||||
|
interRect := pasteRect.Intersect(dst.Bounds())
|
||||||
|
if interRect.Empty() {
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
src := newScanner(img)
|
||||||
|
parallel(interRect.Min.Y, interRect.Max.Y, func(ys <-chan int) {
|
||||||
|
scanLine := make([]uint8, interRect.Dx()*4)
|
||||||
|
for y := range ys {
|
||||||
|
x1 := interRect.Min.X - pasteRect.Min.X
|
||||||
|
x2 := interRect.Max.X - pasteRect.Min.X
|
||||||
|
y1 := y - pasteRect.Min.Y
|
||||||
|
y2 := y1 + 1
|
||||||
|
src.scan(x1, y1, x2, y2, scanLine)
|
||||||
|
i := y*dst.Stride + interRect.Min.X*4
|
||||||
|
j := 0
|
||||||
|
for x := interRect.Min.X; x < interRect.Max.X; x++ {
|
||||||
|
d := dst.Pix[i : i+4 : i+4]
|
||||||
|
r1 := float64(d[0])
|
||||||
|
g1 := float64(d[1])
|
||||||
|
b1 := float64(d[2])
|
||||||
|
a1 := float64(d[3])
|
||||||
|
|
||||||
|
s := scanLine[j : j+4 : j+4]
|
||||||
|
r2 := float64(s[0])
|
||||||
|
g2 := float64(s[1])
|
||||||
|
b2 := float64(s[2])
|
||||||
|
a2 := float64(s[3])
|
||||||
|
|
||||||
|
coef2 := opacity * a2 / 255
|
||||||
|
coef1 := (1 - coef2) * a1 / 255
|
||||||
|
coefSum := coef1 + coef2
|
||||||
|
coef1 /= coefSum
|
||||||
|
coef2 /= coefSum
|
||||||
|
|
||||||
|
d[0] = uint8(r1*coef1 + r2*coef2)
|
||||||
|
d[1] = uint8(g1*coef1 + g2*coef2)
|
||||||
|
d[2] = uint8(b1*coef1 + b2*coef2)
|
||||||
|
d[3] = uint8(math.Min(a1+a2*opacity*(255-a1)/255, 255))
|
||||||
|
|
||||||
|
i += 4
|
||||||
|
j += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// OverlayCenter overlays the img image to the center of the background image and
|
||||||
|
// returns the combined image. Opacity parameter is the opacity of the img
|
||||||
|
// image layer, used to compose the images, it must be from 0.0 to 1.0.
|
||||||
|
func OverlayCenter(background, img image.Image, opacity float64) *image.NRGBA {
|
||||||
|
bgBounds := background.Bounds()
|
||||||
|
bgW := bgBounds.Dx()
|
||||||
|
bgH := bgBounds.Dy()
|
||||||
|
bgMinX := bgBounds.Min.X
|
||||||
|
bgMinY := bgBounds.Min.Y
|
||||||
|
|
||||||
|
centerX := bgMinX + bgW/2
|
||||||
|
centerY := bgMinY + bgH/2
|
||||||
|
|
||||||
|
x0 := centerX - img.Bounds().Dx()/2
|
||||||
|
y0 := centerY - img.Bounds().Dy()/2
|
||||||
|
|
||||||
|
return Overlay(background, img, image.Point{x0, y0}, opacity)
|
||||||
|
}
|
||||||
268
vendor/github.com/disintegration/imaging/transform.go
generated
vendored
Normal file
268
vendor/github.com/disintegration/imaging/transform.go
generated
vendored
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
package imaging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FlipH flips the image horizontally (from left to right) and returns the transformed image.
|
||||||
|
func FlipH(img image.Image) *image.NRGBA {
|
||||||
|
src := newScanner(img)
|
||||||
|
dstW := src.w
|
||||||
|
dstH := src.h
|
||||||
|
rowSize := dstW * 4
|
||||||
|
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
|
||||||
|
parallel(0, dstH, func(ys <-chan int) {
|
||||||
|
for dstY := range ys {
|
||||||
|
i := dstY * dst.Stride
|
||||||
|
srcY := dstY
|
||||||
|
src.scan(0, srcY, src.w, srcY+1, dst.Pix[i:i+rowSize])
|
||||||
|
reverse(dst.Pix[i : i+rowSize])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlipV flips the image vertically (from top to bottom) and returns the transformed image.
|
||||||
|
func FlipV(img image.Image) *image.NRGBA {
|
||||||
|
src := newScanner(img)
|
||||||
|
dstW := src.w
|
||||||
|
dstH := src.h
|
||||||
|
rowSize := dstW * 4
|
||||||
|
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
|
||||||
|
parallel(0, dstH, func(ys <-chan int) {
|
||||||
|
for dstY := range ys {
|
||||||
|
i := dstY * dst.Stride
|
||||||
|
srcY := dstH - dstY - 1
|
||||||
|
src.scan(0, srcY, src.w, srcY+1, dst.Pix[i:i+rowSize])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transpose flips the image horizontally and rotates 90 degrees counter-clockwise.
|
||||||
|
func Transpose(img image.Image) *image.NRGBA {
|
||||||
|
src := newScanner(img)
|
||||||
|
dstW := src.h
|
||||||
|
dstH := src.w
|
||||||
|
rowSize := dstW * 4
|
||||||
|
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
|
||||||
|
parallel(0, dstH, func(ys <-chan int) {
|
||||||
|
for dstY := range ys {
|
||||||
|
i := dstY * dst.Stride
|
||||||
|
srcX := dstY
|
||||||
|
src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transverse flips the image vertically and rotates 90 degrees counter-clockwise.
|
||||||
|
func Transverse(img image.Image) *image.NRGBA {
|
||||||
|
src := newScanner(img)
|
||||||
|
dstW := src.h
|
||||||
|
dstH := src.w
|
||||||
|
rowSize := dstW * 4
|
||||||
|
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
|
||||||
|
parallel(0, dstH, func(ys <-chan int) {
|
||||||
|
for dstY := range ys {
|
||||||
|
i := dstY * dst.Stride
|
||||||
|
srcX := dstH - dstY - 1
|
||||||
|
src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize])
|
||||||
|
reverse(dst.Pix[i : i+rowSize])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotate90 rotates the image 90 degrees counter-clockwise and returns the transformed image.
|
||||||
|
func Rotate90(img image.Image) *image.NRGBA {
|
||||||
|
src := newScanner(img)
|
||||||
|
dstW := src.h
|
||||||
|
dstH := src.w
|
||||||
|
rowSize := dstW * 4
|
||||||
|
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
|
||||||
|
parallel(0, dstH, func(ys <-chan int) {
|
||||||
|
for dstY := range ys {
|
||||||
|
i := dstY * dst.Stride
|
||||||
|
srcX := dstH - dstY - 1
|
||||||
|
src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotate180 rotates the image 180 degrees counter-clockwise and returns the transformed image.
|
||||||
|
func Rotate180(img image.Image) *image.NRGBA {
|
||||||
|
src := newScanner(img)
|
||||||
|
dstW := src.w
|
||||||
|
dstH := src.h
|
||||||
|
rowSize := dstW * 4
|
||||||
|
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
|
||||||
|
parallel(0, dstH, func(ys <-chan int) {
|
||||||
|
for dstY := range ys {
|
||||||
|
i := dstY * dst.Stride
|
||||||
|
srcY := dstH - dstY - 1
|
||||||
|
src.scan(0, srcY, src.w, srcY+1, dst.Pix[i:i+rowSize])
|
||||||
|
reverse(dst.Pix[i : i+rowSize])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotate270 rotates the image 270 degrees counter-clockwise and returns the transformed image.
|
||||||
|
func Rotate270(img image.Image) *image.NRGBA {
|
||||||
|
src := newScanner(img)
|
||||||
|
dstW := src.h
|
||||||
|
dstH := src.w
|
||||||
|
rowSize := dstW * 4
|
||||||
|
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
|
||||||
|
parallel(0, dstH, func(ys <-chan int) {
|
||||||
|
for dstY := range ys {
|
||||||
|
i := dstY * dst.Stride
|
||||||
|
srcX := dstY
|
||||||
|
src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize])
|
||||||
|
reverse(dst.Pix[i : i+rowSize])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotate rotates an image by the given angle counter-clockwise .
|
||||||
|
// The angle parameter is the rotation angle in degrees.
|
||||||
|
// The bgColor parameter specifies the color of the uncovered zone after the rotation.
|
||||||
|
func Rotate(img image.Image, angle float64, bgColor color.Color) *image.NRGBA {
|
||||||
|
angle = angle - math.Floor(angle/360)*360
|
||||||
|
|
||||||
|
switch angle {
|
||||||
|
case 0:
|
||||||
|
return Clone(img)
|
||||||
|
case 90:
|
||||||
|
return Rotate90(img)
|
||||||
|
case 180:
|
||||||
|
return Rotate180(img)
|
||||||
|
case 270:
|
||||||
|
return Rotate270(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
src := toNRGBA(img)
|
||||||
|
srcW := src.Bounds().Max.X
|
||||||
|
srcH := src.Bounds().Max.Y
|
||||||
|
dstW, dstH := rotatedSize(srcW, srcH, angle)
|
||||||
|
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
|
||||||
|
|
||||||
|
if dstW <= 0 || dstH <= 0 {
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
srcXOff := float64(srcW)/2 - 0.5
|
||||||
|
srcYOff := float64(srcH)/2 - 0.5
|
||||||
|
dstXOff := float64(dstW)/2 - 0.5
|
||||||
|
dstYOff := float64(dstH)/2 - 0.5
|
||||||
|
|
||||||
|
bgColorNRGBA := color.NRGBAModel.Convert(bgColor).(color.NRGBA)
|
||||||
|
sin, cos := math.Sincos(math.Pi * angle / 180)
|
||||||
|
|
||||||
|
parallel(0, dstH, func(ys <-chan int) {
|
||||||
|
for dstY := range ys {
|
||||||
|
for dstX := 0; dstX < dstW; dstX++ {
|
||||||
|
xf, yf := rotatePoint(float64(dstX)-dstXOff, float64(dstY)-dstYOff, sin, cos)
|
||||||
|
xf, yf = xf+srcXOff, yf+srcYOff
|
||||||
|
interpolatePoint(dst, dstX, dstY, src, xf, yf, bgColorNRGBA)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
func rotatePoint(x, y, sin, cos float64) (float64, float64) {
|
||||||
|
return x*cos - y*sin, x*sin + y*cos
|
||||||
|
}
|
||||||
|
|
||||||
|
func rotatedSize(w, h int, angle float64) (int, int) {
|
||||||
|
if w <= 0 || h <= 0 {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
sin, cos := math.Sincos(math.Pi * angle / 180)
|
||||||
|
x1, y1 := rotatePoint(float64(w-1), 0, sin, cos)
|
||||||
|
x2, y2 := rotatePoint(float64(w-1), float64(h-1), sin, cos)
|
||||||
|
x3, y3 := rotatePoint(0, float64(h-1), sin, cos)
|
||||||
|
|
||||||
|
minx := math.Min(x1, math.Min(x2, math.Min(x3, 0)))
|
||||||
|
maxx := math.Max(x1, math.Max(x2, math.Max(x3, 0)))
|
||||||
|
miny := math.Min(y1, math.Min(y2, math.Min(y3, 0)))
|
||||||
|
maxy := math.Max(y1, math.Max(y2, math.Max(y3, 0)))
|
||||||
|
|
||||||
|
neww := maxx - minx + 1
|
||||||
|
if neww-math.Floor(neww) > 0.1 {
|
||||||
|
neww++
|
||||||
|
}
|
||||||
|
newh := maxy - miny + 1
|
||||||
|
if newh-math.Floor(newh) > 0.1 {
|
||||||
|
newh++
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(neww), int(newh)
|
||||||
|
}
|
||||||
|
|
||||||
|
func interpolatePoint(dst *image.NRGBA, dstX, dstY int, src *image.NRGBA, xf, yf float64, bgColor color.NRGBA) {
|
||||||
|
j := dstY*dst.Stride + dstX*4
|
||||||
|
d := dst.Pix[j : j+4 : j+4]
|
||||||
|
|
||||||
|
x0 := int(math.Floor(xf))
|
||||||
|
y0 := int(math.Floor(yf))
|
||||||
|
bounds := src.Bounds()
|
||||||
|
if !image.Pt(x0, y0).In(image.Rect(bounds.Min.X-1, bounds.Min.Y-1, bounds.Max.X, bounds.Max.Y)) {
|
||||||
|
d[0] = bgColor.R
|
||||||
|
d[1] = bgColor.G
|
||||||
|
d[2] = bgColor.B
|
||||||
|
d[3] = bgColor.A
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
xq := xf - float64(x0)
|
||||||
|
yq := yf - float64(y0)
|
||||||
|
points := [4]image.Point{
|
||||||
|
{x0, y0},
|
||||||
|
{x0 + 1, y0},
|
||||||
|
{x0, y0 + 1},
|
||||||
|
{x0 + 1, y0 + 1},
|
||||||
|
}
|
||||||
|
weights := [4]float64{
|
||||||
|
(1 - xq) * (1 - yq),
|
||||||
|
xq * (1 - yq),
|
||||||
|
(1 - xq) * yq,
|
||||||
|
xq * yq,
|
||||||
|
}
|
||||||
|
|
||||||
|
var r, g, b, a float64
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
p := points[i]
|
||||||
|
w := weights[i]
|
||||||
|
if p.In(bounds) {
|
||||||
|
i := p.Y*src.Stride + p.X*4
|
||||||
|
s := src.Pix[i : i+4 : i+4]
|
||||||
|
wa := float64(s[3]) * w
|
||||||
|
r += float64(s[0]) * wa
|
||||||
|
g += float64(s[1]) * wa
|
||||||
|
b += float64(s[2]) * wa
|
||||||
|
a += wa
|
||||||
|
} else {
|
||||||
|
wa := float64(bgColor.A) * w
|
||||||
|
r += float64(bgColor.R) * wa
|
||||||
|
g += float64(bgColor.G) * wa
|
||||||
|
b += float64(bgColor.B) * wa
|
||||||
|
a += wa
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if a != 0 {
|
||||||
|
aInv := 1 / a
|
||||||
|
d[0] = clamp(r * aInv)
|
||||||
|
d[1] = clamp(g * aInv)
|
||||||
|
d[2] = clamp(b * aInv)
|
||||||
|
d[3] = clamp(a)
|
||||||
|
}
|
||||||
|
}
|
||||||
167
vendor/github.com/disintegration/imaging/utils.go
generated
vendored
Normal file
167
vendor/github.com/disintegration/imaging/utils.go
generated
vendored
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
package imaging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"math"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// parallel processes the data in separate goroutines.
|
||||||
|
func parallel(start, stop int, fn func(<-chan int)) {
|
||||||
|
count := stop - start
|
||||||
|
if count < 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
procs := runtime.GOMAXPROCS(0)
|
||||||
|
if procs > count {
|
||||||
|
procs = count
|
||||||
|
}
|
||||||
|
|
||||||
|
c := make(chan int, count)
|
||||||
|
for i := start; i < stop; i++ {
|
||||||
|
c <- i
|
||||||
|
}
|
||||||
|
close(c)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i := 0; i < procs; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
fn(c)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// absint returns the absolute value of i.
|
||||||
|
func absint(i int) int {
|
||||||
|
if i < 0 {
|
||||||
|
return -i
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// clamp rounds and clamps float64 value to fit into uint8.
|
||||||
|
func clamp(x float64) uint8 {
|
||||||
|
v := int64(x + 0.5)
|
||||||
|
if v > 255 {
|
||||||
|
return 255
|
||||||
|
}
|
||||||
|
if v > 0 {
|
||||||
|
return uint8(v)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func reverse(pix []uint8) {
|
||||||
|
if len(pix) <= 4 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i := 0
|
||||||
|
j := len(pix) - 4
|
||||||
|
for i < j {
|
||||||
|
pi := pix[i : i+4 : i+4]
|
||||||
|
pj := pix[j : j+4 : j+4]
|
||||||
|
pi[0], pj[0] = pj[0], pi[0]
|
||||||
|
pi[1], pj[1] = pj[1], pi[1]
|
||||||
|
pi[2], pj[2] = pj[2], pi[2]
|
||||||
|
pi[3], pj[3] = pj[3], pi[3]
|
||||||
|
i += 4
|
||||||
|
j -= 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toNRGBA(img image.Image) *image.NRGBA {
|
||||||
|
if img, ok := img.(*image.NRGBA); ok {
|
||||||
|
return &image.NRGBA{
|
||||||
|
Pix: img.Pix,
|
||||||
|
Stride: img.Stride,
|
||||||
|
Rect: img.Rect.Sub(img.Rect.Min),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Clone(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
// rgbToHSL converts a color from RGB to HSL.
|
||||||
|
func rgbToHSL(r, g, b uint8) (float64, float64, float64) {
|
||||||
|
rr := float64(r) / 255
|
||||||
|
gg := float64(g) / 255
|
||||||
|
bb := float64(b) / 255
|
||||||
|
|
||||||
|
max := math.Max(rr, math.Max(gg, bb))
|
||||||
|
min := math.Min(rr, math.Min(gg, bb))
|
||||||
|
|
||||||
|
l := (max + min) / 2
|
||||||
|
|
||||||
|
if max == min {
|
||||||
|
return 0, 0, l
|
||||||
|
}
|
||||||
|
|
||||||
|
var h, s float64
|
||||||
|
d := max - min
|
||||||
|
if l > 0.5 {
|
||||||
|
s = d / (2 - max - min)
|
||||||
|
} else {
|
||||||
|
s = d / (max + min)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch max {
|
||||||
|
case rr:
|
||||||
|
h = (gg - bb) / d
|
||||||
|
if g < b {
|
||||||
|
h += 6
|
||||||
|
}
|
||||||
|
case gg:
|
||||||
|
h = (bb-rr)/d + 2
|
||||||
|
case bb:
|
||||||
|
h = (rr-gg)/d + 4
|
||||||
|
}
|
||||||
|
h /= 6
|
||||||
|
|
||||||
|
return h, s, l
|
||||||
|
}
|
||||||
|
|
||||||
|
// hslToRGB converts a color from HSL to RGB.
|
||||||
|
func hslToRGB(h, s, l float64) (uint8, uint8, uint8) {
|
||||||
|
var r, g, b float64
|
||||||
|
if s == 0 {
|
||||||
|
v := clamp(l * 255)
|
||||||
|
return v, v, v
|
||||||
|
}
|
||||||
|
|
||||||
|
var q float64
|
||||||
|
if l < 0.5 {
|
||||||
|
q = l * (1 + s)
|
||||||
|
} else {
|
||||||
|
q = l + s - l*s
|
||||||
|
}
|
||||||
|
p := 2*l - q
|
||||||
|
|
||||||
|
r = hueToRGB(p, q, h+1/3.0)
|
||||||
|
g = hueToRGB(p, q, h)
|
||||||
|
b = hueToRGB(p, q, h-1/3.0)
|
||||||
|
|
||||||
|
return clamp(r * 255), clamp(g * 255), clamp(b * 255)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hueToRGB(p, q, t float64) float64 {
|
||||||
|
if t < 0 {
|
||||||
|
t++
|
||||||
|
}
|
||||||
|
if t > 1 {
|
||||||
|
t--
|
||||||
|
}
|
||||||
|
if t < 1/6.0 {
|
||||||
|
return p + (q-p)*6*t
|
||||||
|
}
|
||||||
|
if t < 1/2.0 {
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
if t < 2/3.0 {
|
||||||
|
return p + (q-p)*(2/3.0-t)*6
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
8
vendor/github.com/eclipse/paho.mqtt.golang/README.md
generated
vendored
8
vendor/github.com/eclipse/paho.mqtt.golang/README.md
generated
vendored
@@ -104,8 +104,12 @@ func main() {
|
|||||||
|
|
||||||
* Seemingly random disconnections may be caused by another client connecting to the broker with the same client
|
* Seemingly random disconnections may be caused by another client connecting to the broker with the same client
|
||||||
identifier; this is as per the [spec](https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc384800405).
|
identifier; this is as per the [spec](https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc384800405).
|
||||||
* A `MessageHandler` (called when a new message is received) must not block. If you wish to perform a long-running task,
|
* Unless ordered delivery of messages is essential (and you have configured your broker to support this e.g.
|
||||||
or publish a message, then please use a go routine (blocking in the handler is a common cause of unexpected `pingresp
|
`max_inflight_messages=1` in mosquitto) then set `ClientOptions.SetOrderMatters(false)`. Doing so will avoid the
|
||||||
|
below issue (deadlocks due to blocking message handlers).
|
||||||
|
* A `MessageHandler` (called when a new message is received) must not block (unless
|
||||||
|
`ClientOptions.SetOrderMatters(false)` set). If you wish to perform a long-running task, or publish a message, then
|
||||||
|
please use a go routine (blocking in the handler is a common cause of unexpected `pingresp
|
||||||
not received, disconnecting` errors).
|
not received, disconnecting` errors).
|
||||||
* When QOS1+ subscriptions have been created previously and you connect with `CleanSession` set to false it is possible that the broker will deliver retained
|
* When QOS1+ subscriptions have been created previously and you connect with `CleanSession` set to false it is possible that the broker will deliver retained
|
||||||
messages before `Subscribe` can be called. To process these messages either configure a handler with `AddRoute` or
|
messages before `Subscribe` can be called. To process these messages either configure a handler with `AddRoute` or
|
||||||
|
|||||||
106
vendor/github.com/eclipse/paho.mqtt.golang/client.go
generated
vendored
106
vendor/github.com/eclipse/paho.mqtt.golang/client.go
generated
vendored
@@ -55,6 +55,8 @@ const (
|
|||||||
// information can be found in their respective documentation.
|
// information can be found in their respective documentation.
|
||||||
// Numerous connection options may be specified by configuring a
|
// Numerous connection options may be specified by configuring a
|
||||||
// and then supplying a ClientOptions type.
|
// and then supplying a ClientOptions type.
|
||||||
|
// Implementations of Client must be safe for concurrent use by multiple
|
||||||
|
// goroutines
|
||||||
type Client interface {
|
type Client interface {
|
||||||
// IsConnected returns a bool signifying whether
|
// IsConnected returns a bool signifying whether
|
||||||
// the client is connected or not.
|
// the client is connected or not.
|
||||||
@@ -75,11 +77,21 @@ type Client interface {
|
|||||||
// Returns a token to track delivery of the message to the broker
|
// Returns a token to track delivery of the message to the broker
|
||||||
Publish(topic string, qos byte, retained bool, payload interface{}) Token
|
Publish(topic string, qos byte, retained bool, payload interface{}) Token
|
||||||
// Subscribe starts a new subscription. Provide a MessageHandler to be executed when
|
// Subscribe starts a new subscription. Provide a MessageHandler to be executed when
|
||||||
// a message is published on the topic provided, or nil for the default handler
|
// a message is published on the topic provided, or nil for the default handler.
|
||||||
|
//
|
||||||
|
// If options.OrderMatters is true (the default) then callback must not block or
|
||||||
|
// call functions within this package that may block (e.g. Publish) other than in
|
||||||
|
// a new go routine.
|
||||||
|
// callback must be safe for concurrent use by multiple goroutines.
|
||||||
Subscribe(topic string, qos byte, callback MessageHandler) Token
|
Subscribe(topic string, qos byte, callback MessageHandler) Token
|
||||||
// SubscribeMultiple starts a new subscription for multiple topics. Provide a MessageHandler to
|
// SubscribeMultiple starts a new subscription for multiple topics. Provide a MessageHandler to
|
||||||
// be executed when a message is published on one of the topics provided, or nil for the
|
// be executed when a message is published on one of the topics provided, or nil for the
|
||||||
// default handler
|
// default handler.
|
||||||
|
//
|
||||||
|
// If options.OrderMatters is true (the default) then callback must not block or
|
||||||
|
// call functions within this package that may block (e.g. Publish) other than in
|
||||||
|
// a new go routine.
|
||||||
|
// callback must be safe for concurrent use by multiple goroutines.
|
||||||
SubscribeMultiple(filters map[string]byte, callback MessageHandler) Token
|
SubscribeMultiple(filters map[string]byte, callback MessageHandler) Token
|
||||||
// Unsubscribe will end the subscription from each of the topics provided.
|
// Unsubscribe will end the subscription from each of the topics provided.
|
||||||
// Messages published to those topics from other clients will no longer be
|
// Messages published to those topics from other clients will no longer be
|
||||||
@@ -87,7 +99,13 @@ type Client interface {
|
|||||||
Unsubscribe(topics ...string) Token
|
Unsubscribe(topics ...string) Token
|
||||||
// AddRoute allows you to add a handler for messages on a specific topic
|
// AddRoute allows you to add a handler for messages on a specific topic
|
||||||
// without making a subscription. For example having a different handler
|
// without making a subscription. For example having a different handler
|
||||||
// for parts of a wildcard subscription
|
// for parts of a wildcard subscription or for receiving retained messages
|
||||||
|
// upon connection (before Sub scribe can be processed).
|
||||||
|
//
|
||||||
|
// If options.OrderMatters is true (the default) then callback must not block or
|
||||||
|
// call functions within this package that may block (e.g. Publish) other than in
|
||||||
|
// a new go routine.
|
||||||
|
// callback must be safe for concurrent use by multiple goroutines.
|
||||||
AddRoute(topic string, callback MessageHandler)
|
AddRoute(topic string, callback MessageHandler)
|
||||||
// OptionsReader returns a ClientOptionsReader which is a copy of the clientoptions
|
// OptionsReader returns a ClientOptionsReader which is a copy of the clientoptions
|
||||||
// in use by the client.
|
// in use by the client.
|
||||||
@@ -95,6 +113,8 @@ type Client interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// client implements the Client interface
|
// client implements the Client interface
|
||||||
|
// clients are safe for concurrent use by multiple
|
||||||
|
// goroutines
|
||||||
type client struct {
|
type client struct {
|
||||||
lastSent atomic.Value // time.Time - the last time a packet was successfully sent to network
|
lastSent atomic.Value // time.Time - the last time a packet was successfully sent to network
|
||||||
lastReceived atomic.Value // time.Time - the last time a packet was successfully received from network
|
lastReceived atomic.Value // time.Time - the last time a packet was successfully received from network
|
||||||
@@ -153,6 +173,11 @@ func NewClient(o *ClientOptions) Client {
|
|||||||
// AddRoute allows you to add a handler for messages on a specific topic
|
// AddRoute allows you to add a handler for messages on a specific topic
|
||||||
// without making a subscription. For example having a different handler
|
// without making a subscription. For example having a different handler
|
||||||
// for parts of a wildcard subscription
|
// for parts of a wildcard subscription
|
||||||
|
//
|
||||||
|
// If options.OrderMatters is true (the default) then callback must not block or
|
||||||
|
// call functions within this package that may block (e.g. Publish) other than in
|
||||||
|
// a new go routine.
|
||||||
|
// callback must be safe for concurrent use by multiple goroutines.
|
||||||
func (c *client) AddRoute(topic string, callback MessageHandler) {
|
func (c *client) AddRoute(topic string, callback MessageHandler) {
|
||||||
if callback != nil {
|
if callback != nil {
|
||||||
c.msgRouter.addRoute(topic, callback)
|
c.msgRouter.addRoute(topic, callback)
|
||||||
@@ -354,8 +379,13 @@ func (c *client) attemptConnection() (net.Conn, byte, bool, error) {
|
|||||||
cm := newConnectMsgFromOptions(&c.options, broker)
|
cm := newConnectMsgFromOptions(&c.options, broker)
|
||||||
DEBUG.Println(CLI, "about to write new connect msg")
|
DEBUG.Println(CLI, "about to write new connect msg")
|
||||||
CONN:
|
CONN:
|
||||||
|
tlsCfg := c.options.TLSConfig
|
||||||
|
if c.options.OnConnectAttempt != nil {
|
||||||
|
DEBUG.Println(CLI, "using custom onConnectAttempt handler...")
|
||||||
|
tlsCfg = c.options.OnConnectAttempt(broker, c.options.TLSConfig)
|
||||||
|
}
|
||||||
// Start by opening the network connection (tcp, tls, ws) etc
|
// Start by opening the network connection (tcp, tls, ws) etc
|
||||||
conn, err = openConnection(broker, c.options.TLSConfig, c.options.ConnectTimeout, c.options.HTTPHeaders, c.options.WebsocketOptions)
|
conn, err = openConnection(broker, tlsCfg, c.options.ConnectTimeout, c.options.HTTPHeaders, c.options.WebsocketOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ERROR.Println(CLI, err.Error())
|
ERROR.Println(CLI, err.Error())
|
||||||
WARN.Println(CLI, "failed to connect to broker, trying next")
|
WARN.Println(CLI, "failed to connect to broker, trying next")
|
||||||
@@ -372,7 +402,7 @@ func (c *client) attemptConnection() (net.Conn, byte, bool, error) {
|
|||||||
|
|
||||||
// We may be have to attempt the connection with MQTT 3.1
|
// We may be have to attempt the connection with MQTT 3.1
|
||||||
if conn != nil {
|
if conn != nil {
|
||||||
conn.Close()
|
_ = conn.Close()
|
||||||
}
|
}
|
||||||
if !c.options.protocolVersionExplicit && protocolVersion == 4 { // try falling back to 3.1?
|
if !c.options.protocolVersionExplicit && protocolVersion == 4 { // try falling back to 3.1?
|
||||||
DEBUG.Println(CLI, "Trying reconnect using MQTT 3.1 protocol")
|
DEBUG.Println(CLI, "Trying reconnect using MQTT 3.1 protocol")
|
||||||
@@ -409,12 +439,22 @@ func (c *client) Disconnect(quiesce uint) {
|
|||||||
|
|
||||||
dm := packets.NewControlPacket(packets.Disconnect).(*packets.DisconnectPacket)
|
dm := packets.NewControlPacket(packets.Disconnect).(*packets.DisconnectPacket)
|
||||||
dt := newToken(packets.Disconnect)
|
dt := newToken(packets.Disconnect)
|
||||||
c.oboundP <- &PacketAndToken{p: dm, t: dt}
|
disconnectSent := false
|
||||||
|
select {
|
||||||
|
case c.oboundP <- &PacketAndToken{p: dm, t: dt}:
|
||||||
|
disconnectSent = true
|
||||||
|
case <-c.commsStopped:
|
||||||
|
WARN.Println("Disconnect packet could not be sent because comms stopped")
|
||||||
|
case <-time.After(time.Duration(quiesce) * time.Millisecond):
|
||||||
|
WARN.Println("Disconnect packet not sent due to timeout")
|
||||||
|
}
|
||||||
|
|
||||||
// wait for work to finish, or quiesce time consumed
|
// wait for work to finish, or quiesce time consumed
|
||||||
|
if disconnectSent {
|
||||||
DEBUG.Println(CLI, "calling WaitTimeout")
|
DEBUG.Println(CLI, "calling WaitTimeout")
|
||||||
dt.WaitTimeout(time.Duration(quiesce) * time.Millisecond)
|
dt.WaitTimeout(time.Duration(quiesce) * time.Millisecond)
|
||||||
DEBUG.Println(CLI, "WaitTimeout done")
|
DEBUG.Println(CLI, "WaitTimeout done")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
WARN.Println(CLI, "Disconnect() called but not connected (disconnected/reconnecting)")
|
WARN.Println(CLI, "Disconnect() called but not connected (disconnected/reconnecting)")
|
||||||
c.setConnected(disconnected)
|
c.setConnected(disconnected)
|
||||||
@@ -459,10 +499,13 @@ func (c *client) internalConnLost(err error) {
|
|||||||
DEBUG.Println(CLI, "internalConnLost waiting on workers")
|
DEBUG.Println(CLI, "internalConnLost waiting on workers")
|
||||||
<-stopDone
|
<-stopDone
|
||||||
DEBUG.Println(CLI, "internalConnLost workers stopped")
|
DEBUG.Println(CLI, "internalConnLost workers stopped")
|
||||||
if c.options.CleanSession && !c.options.AutoReconnect {
|
// It is possible that Disconnect was called which led to this error so reconnection depends upon status
|
||||||
|
reconnect := c.options.AutoReconnect && c.connectionStatus() > connecting
|
||||||
|
|
||||||
|
if c.options.CleanSession && !reconnect {
|
||||||
c.messageIds.cleanUp()
|
c.messageIds.cleanUp()
|
||||||
}
|
}
|
||||||
if c.options.AutoReconnect {
|
if reconnect {
|
||||||
c.setConnected(reconnecting)
|
c.setConnected(reconnecting)
|
||||||
go c.reconnect()
|
go c.reconnect()
|
||||||
} else {
|
} else {
|
||||||
@@ -476,8 +519,8 @@ func (c *client) internalConnLost(err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// startCommsWorkers is called when the connection is up. It starts off all of the routines needed to process incoming and
|
// startCommsWorkers is called when the connection is up.
|
||||||
// outgoing messages.
|
// It starts off all of the routines needed to process incoming and outgoing messages.
|
||||||
// Returns true if the comms workers were started (i.e. they were not already running)
|
// Returns true if the comms workers were started (i.e. they were not already running)
|
||||||
func (c *client) startCommsWorkers(conn net.Conn, inboundFromStore <-chan packets.ControlPacket) bool {
|
func (c *client) startCommsWorkers(conn net.Conn, inboundFromStore <-chan packets.ControlPacket) bool {
|
||||||
DEBUG.Println(CLI, "startCommsWorkers called")
|
DEBUG.Println(CLI, "startCommsWorkers called")
|
||||||
@@ -564,7 +607,22 @@ func (c *client) startCommsWorkers(conn net.Conn, inboundFromStore <-chan packet
|
|||||||
commsIncomingPub = nil
|
commsIncomingPub = nil
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
incomingPubChan <- pub
|
// Care is needed here because an error elsewhere could trigger a deadlock
|
||||||
|
sendPubLoop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case incomingPubChan <- pub:
|
||||||
|
break sendPubLoop
|
||||||
|
case err, ok := <-commsErrors:
|
||||||
|
if !ok { // commsErrors has been closed so we can ignore it
|
||||||
|
commsErrors = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ERROR.Println(CLI, "Connect comms goroutine - error triggered during send Pub", err)
|
||||||
|
c.internalConnLost(err) // no harm in calling this if the connection is already down (or shutdown is in progress)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
case err, ok := <-commsErrors:
|
case err, ok := <-commsErrors:
|
||||||
if !ok {
|
if !ok {
|
||||||
commsErrors = nil
|
commsErrors = nil
|
||||||
@@ -686,10 +744,10 @@ func (c *client) Publish(topic string, qos byte, retained bool, payload interfac
|
|||||||
// Subscribe starts a new subscription. Provide a MessageHandler to be executed when
|
// Subscribe starts a new subscription. Provide a MessageHandler to be executed when
|
||||||
// a message is published on the topic provided.
|
// a message is published on the topic provided.
|
||||||
//
|
//
|
||||||
// Please note: you should try to keep the execution time of the callback to be
|
// If options.OrderMatters is true (the default) then callback must not block or
|
||||||
// as low as possible, especially when SetOrderMatters(true) (the default) is in
|
// call functions within this package that may block (e.g. Publish) other than in
|
||||||
// place. Blocking calls in message handlers might otherwise delay delivery to
|
// a new go routine.
|
||||||
// other message handlers.
|
// callback must be safe for concurrent use by multiple goroutines.
|
||||||
func (c *client) Subscribe(topic string, qos byte, callback MessageHandler) Token {
|
func (c *client) Subscribe(topic string, qos byte, callback MessageHandler) Token {
|
||||||
token := newToken(packets.Subscribe).(*SubscribeToken)
|
token := newToken(packets.Subscribe).(*SubscribeToken)
|
||||||
DEBUG.Println(CLI, "enter Subscribe")
|
DEBUG.Println(CLI, "enter Subscribe")
|
||||||
@@ -766,6 +824,11 @@ func (c *client) Subscribe(topic string, qos byte, callback MessageHandler) Toke
|
|||||||
|
|
||||||
// SubscribeMultiple starts a new subscription for multiple topics. Provide a MessageHandler to
|
// SubscribeMultiple starts a new subscription for multiple topics. Provide a MessageHandler to
|
||||||
// be executed when a message is published on one of the topics provided.
|
// be executed when a message is published on one of the topics provided.
|
||||||
|
//
|
||||||
|
// If options.OrderMatters is true (the default) then callback must not block or
|
||||||
|
// call functions within this package that may block (e.g. Publish) other than in
|
||||||
|
// a new go routine.
|
||||||
|
// callback must be safe for concurrent use by multiple goroutines.
|
||||||
func (c *client) SubscribeMultiple(filters map[string]byte, callback MessageHandler) Token {
|
func (c *client) SubscribeMultiple(filters map[string]byte, callback MessageHandler) Token {
|
||||||
var err error
|
var err error
|
||||||
token := newToken(packets.Subscribe).(*SubscribeToken)
|
token := newToken(packets.Subscribe).(*SubscribeToken)
|
||||||
@@ -869,7 +932,7 @@ func (c *client) resume(subscription bool, ibound chan packets.ControlPacket) {
|
|||||||
}
|
}
|
||||||
details := packet.Details()
|
details := packet.Details()
|
||||||
if isKeyOutbound(key) {
|
if isKeyOutbound(key) {
|
||||||
switch packet.(type) {
|
switch p := packet.(type) {
|
||||||
case *packets.SubscribePacket:
|
case *packets.SubscribePacket:
|
||||||
if subscription {
|
if subscription {
|
||||||
DEBUG.Println(STR, fmt.Sprintf("loaded pending subscribe (%d)", details.MessageID))
|
DEBUG.Println(STR, fmt.Sprintf("loaded pending subscribe (%d)", details.MessageID))
|
||||||
@@ -909,13 +972,22 @@ func (c *client) resume(subscription bool, ibound chan packets.ControlPacket) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
case *packets.PublishPacket:
|
case *packets.PublishPacket:
|
||||||
|
// spec: If the DUP flag is set to 0, it indicates that this is the first occasion that the Client or
|
||||||
|
// Server has attempted to send this MQTT PUBLISH Packet. If the DUP flag is set to 1, it indicates that
|
||||||
|
// this might be re-delivery of an earlier attempt to send the Packet.
|
||||||
|
//
|
||||||
|
// If the message is in the store than an attempt at delivery has been made (note that the message may
|
||||||
|
// never have made it onto the wire but tracking that would be complicated!).
|
||||||
|
if p.Qos != 0 { // spec: The DUP flag MUST be set to 0 for all QoS 0 messages
|
||||||
|
p.Dup = true
|
||||||
|
}
|
||||||
token := newToken(packets.Publish).(*PublishToken)
|
token := newToken(packets.Publish).(*PublishToken)
|
||||||
token.messageID = details.MessageID
|
token.messageID = details.MessageID
|
||||||
c.claimID(token, details.MessageID)
|
c.claimID(token, details.MessageID)
|
||||||
DEBUG.Println(STR, fmt.Sprintf("loaded pending publish (%d)", details.MessageID))
|
DEBUG.Println(STR, fmt.Sprintf("loaded pending publish (%d)", details.MessageID))
|
||||||
DEBUG.Println(STR, details)
|
DEBUG.Println(STR, details)
|
||||||
select {
|
select {
|
||||||
case c.obound <- &PacketAndToken{p: packet, t: token}:
|
case c.obound <- &PacketAndToken{p: p, t: token}:
|
||||||
case <-c.stop:
|
case <-c.stop:
|
||||||
DEBUG.Println(STR, "resume exiting due to stop")
|
DEBUG.Println(STR, "resume exiting due to stop")
|
||||||
return
|
return
|
||||||
|
|||||||
8
vendor/github.com/eclipse/paho.mqtt.golang/go.mod
generated
vendored
8
vendor/github.com/eclipse/paho.mqtt.golang/go.mod
generated
vendored
@@ -1,8 +0,0 @@
|
|||||||
module github.com/eclipse/paho.mqtt.golang
|
|
||||||
|
|
||||||
go 1.14
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/gorilla/websocket v1.4.2
|
|
||||||
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0
|
|
||||||
)
|
|
||||||
8
vendor/github.com/eclipse/paho.mqtt.golang/go.sum
generated
vendored
8
vendor/github.com/eclipse/paho.mqtt.golang/go.sum
generated
vendored
@@ -1,8 +0,0 @@
|
|||||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U=
|
|
||||||
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
5
vendor/github.com/eclipse/paho.mqtt.golang/netconn.go
generated
vendored
5
vendor/github.com/eclipse/paho.mqtt.golang/netconn.go
generated
vendored
@@ -30,7 +30,8 @@ import (
|
|||||||
// This just establishes the network connection; once established the type of connection should be irrelevant
|
// This just establishes the network connection; once established the type of connection should be irrelevant
|
||||||
//
|
//
|
||||||
|
|
||||||
// openConnection opens a network connection using the protocol indicated in the URL. Does not carry out any MQTT specific handshakes
|
// openConnection opens a network connection using the protocol indicated in the URL.
|
||||||
|
// Does not carry out any MQTT specific handshakes.
|
||||||
func openConnection(uri *url.URL, tlsc *tls.Config, timeout time.Duration, headers http.Header, websocketOptions *WebsocketOptions) (net.Conn, error) {
|
func openConnection(uri *url.URL, tlsc *tls.Config, timeout time.Duration, headers http.Header, websocketOptions *WebsocketOptions) (net.Conn, error) {
|
||||||
switch uri.Scheme {
|
switch uri.Scheme {
|
||||||
case "ws":
|
case "ws":
|
||||||
@@ -81,7 +82,7 @@ func openConnection(uri *url.URL, tlsc *tls.Config, timeout time.Duration, heade
|
|||||||
|
|
||||||
err = tlsConn.Handshake()
|
err = tlsConn.Handshake()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
_ = conn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
32
vendor/github.com/eclipse/paho.mqtt.golang/options.go
generated
vendored
32
vendor/github.com/eclipse/paho.mqtt.golang/options.go
generated
vendored
@@ -21,7 +21,6 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -50,7 +49,11 @@ type OnConnectHandler func(Client)
|
|||||||
// the initial connection is lost
|
// the initial connection is lost
|
||||||
type ReconnectHandler func(Client, *ClientOptions)
|
type ReconnectHandler func(Client, *ClientOptions)
|
||||||
|
|
||||||
// ClientOptions contains configurable options for an Client.
|
// ConnectionAttemptHandler is invoked prior to making the initial connection.
|
||||||
|
type ConnectionAttemptHandler func(broker *url.URL, tlsCfg *tls.Config) *tls.Config
|
||||||
|
|
||||||
|
// ClientOptions contains configurable options for an Client. Note that these should be set using the
|
||||||
|
// relevant methods (e.g. AddBroker) rather than directly. See those functions for information on usage.
|
||||||
type ClientOptions struct {
|
type ClientOptions struct {
|
||||||
Servers []*url.URL
|
Servers []*url.URL
|
||||||
ClientID string
|
ClientID string
|
||||||
@@ -79,6 +82,7 @@ type ClientOptions struct {
|
|||||||
OnConnect OnConnectHandler
|
OnConnect OnConnectHandler
|
||||||
OnConnectionLost ConnectionLostHandler
|
OnConnectionLost ConnectionLostHandler
|
||||||
OnReconnecting ReconnectHandler
|
OnReconnecting ReconnectHandler
|
||||||
|
OnConnectAttempt ConnectionAttemptHandler
|
||||||
WriteTimeout time.Duration
|
WriteTimeout time.Duration
|
||||||
MessageChannelDepth uint
|
MessageChannelDepth uint
|
||||||
ResumeSubs bool
|
ResumeSubs bool
|
||||||
@@ -90,7 +94,7 @@ type ClientOptions struct {
|
|||||||
// default values.
|
// default values.
|
||||||
// Port: 1883
|
// Port: 1883
|
||||||
// CleanSession: True
|
// CleanSession: True
|
||||||
// Order: True
|
// Order: True (note: it is recommended that this be set to FALSE unless order is important)
|
||||||
// KeepAlive: 30 (seconds)
|
// KeepAlive: 30 (seconds)
|
||||||
// ConnectTimeout: 30 (seconds)
|
// ConnectTimeout: 30 (seconds)
|
||||||
// MaxReconnectInterval 10 (minutes)
|
// MaxReconnectInterval 10 (minutes)
|
||||||
@@ -120,6 +124,7 @@ func NewClientOptions() *ClientOptions {
|
|||||||
Store: nil,
|
Store: nil,
|
||||||
OnConnect: nil,
|
OnConnect: nil,
|
||||||
OnConnectionLost: DefaultConnectionLostHandler,
|
OnConnectionLost: DefaultConnectionLostHandler,
|
||||||
|
OnConnectAttempt: nil,
|
||||||
WriteTimeout: 0, // 0 represents timeout disabled
|
WriteTimeout: 0, // 0 represents timeout disabled
|
||||||
ResumeSubs: false,
|
ResumeSubs: false,
|
||||||
HTTPHeaders: make(map[string][]string),
|
HTTPHeaders: make(map[string][]string),
|
||||||
@@ -137,14 +142,12 @@ func NewClientOptions() *ClientOptions {
|
|||||||
//
|
//
|
||||||
// An example broker URI would look like: tcp://foobar.com:1883
|
// An example broker URI would look like: tcp://foobar.com:1883
|
||||||
func (o *ClientOptions) AddBroker(server string) *ClientOptions {
|
func (o *ClientOptions) AddBroker(server string) *ClientOptions {
|
||||||
re := regexp.MustCompile(`%(25)?`)
|
|
||||||
if len(server) > 0 && server[0] == ':' {
|
if len(server) > 0 && server[0] == ':' {
|
||||||
server = "127.0.0.1" + server
|
server = "127.0.0.1" + server
|
||||||
}
|
}
|
||||||
if !strings.Contains(server, "://") {
|
if !strings.Contains(server, "://") {
|
||||||
server = "tcp://" + server
|
server = "tcp://" + server
|
||||||
}
|
}
|
||||||
server = re.ReplaceAllLiteralString(server, "%25")
|
|
||||||
brokerURI, err := url.Parse(server)
|
brokerURI, err := url.Parse(server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ERROR.Println(CLI, "Failed to parse %q broker address: %s", server, err)
|
ERROR.Println(CLI, "Failed to parse %q broker address: %s", server, err)
|
||||||
@@ -206,10 +209,13 @@ func (o *ClientOptions) SetCleanSession(clean bool) *ClientOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetOrderMatters will set the message routing to guarantee order within
|
// SetOrderMatters will set the message routing to guarantee order within
|
||||||
// each QoS level. By default, this value is true. If set to false,
|
// each QoS level. By default, this value is true. If set to false (recommended),
|
||||||
// this flag indicates that messages can be delivered asynchronously
|
// this flag indicates that messages can be delivered asynchronously
|
||||||
// from the client to the application and possibly arrive out of order.
|
// from the client to the application and possibly arrive out of order.
|
||||||
// Specifically, the message handler is called in its own go routine.
|
// Specifically, the message handler is called in its own go routine.
|
||||||
|
// Note that setting this to true does not guarantee in-order delivery
|
||||||
|
// (this is subject to broker settings like "max_inflight_messages=1" in mosquitto)
|
||||||
|
// and if true then handlers must not block.
|
||||||
func (o *ClientOptions) SetOrderMatters(order bool) *ClientOptions {
|
func (o *ClientOptions) SetOrderMatters(order bool) *ClientOptions {
|
||||||
o.Order = order
|
o.Order = order
|
||||||
return o
|
return o
|
||||||
@@ -289,6 +295,11 @@ func (o *ClientOptions) SetBinaryWill(topic string, payload []byte, qos byte, re
|
|||||||
|
|
||||||
// SetDefaultPublishHandler sets the MessageHandler that will be called when a message
|
// SetDefaultPublishHandler sets the MessageHandler that will be called when a message
|
||||||
// is received that does not match any known subscriptions.
|
// is received that does not match any known subscriptions.
|
||||||
|
//
|
||||||
|
// If OrderMatters is true (the defaultHandler) then callback must not block or
|
||||||
|
// call functions within this package that may block (e.g. Publish) other than in
|
||||||
|
// a new go routine.
|
||||||
|
// defaultHandler must be safe for concurrent use by multiple goroutines.
|
||||||
func (o *ClientOptions) SetDefaultPublishHandler(defaultHandler MessageHandler) *ClientOptions {
|
func (o *ClientOptions) SetDefaultPublishHandler(defaultHandler MessageHandler) *ClientOptions {
|
||||||
o.DefaultPublishHandler = defaultHandler
|
o.DefaultPublishHandler = defaultHandler
|
||||||
return o
|
return o
|
||||||
@@ -315,6 +326,15 @@ func (o *ClientOptions) SetReconnectingHandler(cb ReconnectHandler) *ClientOptio
|
|||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetConnectionAttemptHandler sets the ConnectionAttemptHandler callback to be executed prior
|
||||||
|
// to each attempt to connect to an MQTT broker. Returns the *tls.Config that will be used when establishing
|
||||||
|
// the connection (a copy of the tls.Config from ClientOptions will be passed in along with the broker URL).
|
||||||
|
// This allows connection specific changes to be made to the *tls.Config.
|
||||||
|
func (o *ClientOptions) SetConnectionAttemptHandler(onConnectAttempt ConnectionAttemptHandler) *ClientOptions {
|
||||||
|
o.OnConnectAttempt = onConnectAttempt
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
// SetWriteTimeout puts a limit on how long a mqtt publish should block until it unblocks with a
|
// SetWriteTimeout puts a limit on how long a mqtt publish should block until it unblocks with a
|
||||||
// timeout error. A duration of 0 never times out. Default never times out
|
// timeout error. A duration of 0 never times out. Default never times out
|
||||||
func (o *ClientOptions) SetWriteTimeout(t time.Duration) *ClientOptions {
|
func (o *ClientOptions) SetWriteTimeout(t time.Duration) *ClientOptions {
|
||||||
|
|||||||
6
vendor/github.com/eclipse/paho.mqtt.golang/packets/connect.go
generated
vendored
6
vendor/github.com/eclipse/paho.mqtt.golang/packets/connect.go
generated
vendored
@@ -29,7 +29,11 @@ type ConnectPacket struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConnectPacket) String() string {
|
func (c *ConnectPacket) String() string {
|
||||||
return fmt.Sprintf("%s protocolversion: %d protocolname: %s cleansession: %t willflag: %t WillQos: %d WillRetain: %t Usernameflag: %t Passwordflag: %t keepalive: %d clientId: %s willtopic: %s willmessage: %s Username: %s Password: %s", c.FixedHeader, c.ProtocolVersion, c.ProtocolName, c.CleanSession, c.WillFlag, c.WillQos, c.WillRetain, c.UsernameFlag, c.PasswordFlag, c.Keepalive, c.ClientIdentifier, c.WillTopic, c.WillMessage, c.Username, c.Password)
|
var password string
|
||||||
|
if len(c.Password) > 0 {
|
||||||
|
password = "<redacted>"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s protocolversion: %d protocolname: %s cleansession: %t willflag: %t WillQos: %d WillRetain: %t Usernameflag: %t Passwordflag: %t keepalive: %d clientId: %s willtopic: %s willmessage: %s Username: %s Password: %s", c.FixedHeader, c.ProtocolVersion, c.ProtocolName, c.CleanSession, c.WillFlag, c.WillQos, c.WillRetain, c.UsernameFlag, c.PasswordFlag, c.Keepalive, c.ClientIdentifier, c.WillTopic, c.WillMessage, c.Username, password)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConnectPacket) Write(w io.Writer) error {
|
func (c *ConnectPacket) Write(w io.Writer) error {
|
||||||
|
|||||||
24
vendor/github.com/eclipse/paho.mqtt.golang/packets/packets.go
generated
vendored
24
vendor/github.com/eclipse/paho.mqtt.golang/packets/packets.go
generated
vendored
@@ -81,17 +81,27 @@ var ConnackReturnCodes = map[uint8]string{
|
|||||||
255: "Connection Refused: Protocol Violation",
|
255: "Connection Refused: Protocol Violation",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrorRefusedBadProtocolVersion = errors.New("unacceptable protocol version")
|
||||||
|
ErrorRefusedIDRejected = errors.New("identifier rejected")
|
||||||
|
ErrorRefusedServerUnavailable = errors.New("server Unavailable")
|
||||||
|
ErrorRefusedBadUsernameOrPassword = errors.New("bad user name or password")
|
||||||
|
ErrorRefusedNotAuthorised = errors.New("not Authorized")
|
||||||
|
ErrorNetworkError = errors.New("network Error")
|
||||||
|
ErrorProtocolViolation = errors.New("protocol Violation")
|
||||||
|
)
|
||||||
|
|
||||||
// ConnErrors is a map of the errors codes constants for Connect()
|
// ConnErrors is a map of the errors codes constants for Connect()
|
||||||
// to a Go error
|
// to a Go error
|
||||||
var ConnErrors = map[byte]error{
|
var ConnErrors = map[byte]error{
|
||||||
Accepted: nil,
|
Accepted: nil,
|
||||||
ErrRefusedBadProtocolVersion: errors.New("unacceptable protocol version"),
|
ErrRefusedBadProtocolVersion: ErrorRefusedBadProtocolVersion,
|
||||||
ErrRefusedIDRejected: errors.New("identifier rejected"),
|
ErrRefusedIDRejected: ErrorRefusedIDRejected,
|
||||||
ErrRefusedServerUnavailable: errors.New("server Unavailable"),
|
ErrRefusedServerUnavailable: ErrorRefusedServerUnavailable,
|
||||||
ErrRefusedBadUsernameOrPassword: errors.New("bad user name or password"),
|
ErrRefusedBadUsernameOrPassword: ErrorRefusedBadUsernameOrPassword,
|
||||||
ErrRefusedNotAuthorised: errors.New("not Authorized"),
|
ErrRefusedNotAuthorised: ErrorRefusedNotAuthorised,
|
||||||
ErrNetworkError: errors.New("network Error"),
|
ErrNetworkError: ErrorNetworkError,
|
||||||
ErrProtocolViolation: errors.New("protocol Violation"),
|
ErrProtocolViolation: ErrorProtocolViolation,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadPacket takes an instance of an io.Reader (such as net.Conn) and attempts
|
// ReadPacket takes an instance of an io.Reader (such as net.Conn) and attempts
|
||||||
|
|||||||
57
vendor/github.com/eclipse/paho.mqtt.golang/router.go
generated
vendored
57
vendor/github.com/eclipse/paho.mqtt.golang/router.go
generated
vendored
@@ -132,13 +132,46 @@ func (r *router) setDefaultHandler(handler MessageHandler) {
|
|||||||
// associated callback (or the defaultHandler, if one exists and no other route matched). If
|
// associated callback (or the defaultHandler, if one exists and no other route matched). If
|
||||||
// anything is sent down the stop channel the function will end.
|
// anything is sent down the stop channel the function will end.
|
||||||
func (r *router) matchAndDispatch(messages <-chan *packets.PublishPacket, order bool, client *client) <-chan *PacketAndToken {
|
func (r *router) matchAndDispatch(messages <-chan *packets.PublishPacket, order bool, client *client) <-chan *PacketAndToken {
|
||||||
ackChan := make(chan *PacketAndToken)
|
var wg sync.WaitGroup
|
||||||
go func() {
|
ackOutChan := make(chan *PacketAndToken) // Channel returned to caller; closed when messages channel closed
|
||||||
|
var ackInChan chan *PacketAndToken // ACKs generated by ackFunc get put onto this channel
|
||||||
|
|
||||||
|
stopAckCopy := make(chan struct{}) // Closure requests stop of go routine copying ackInChan to ackOutChan
|
||||||
|
ackCopyStopped := make(chan struct{}) // Closure indicates that it is safe to close ackOutChan
|
||||||
|
goRoutinesDone := make(chan struct{}) // closed on wg.Done()
|
||||||
|
if order {
|
||||||
|
ackInChan = ackOutChan // When order = true no go routines are used so safe to use one channel and close when done
|
||||||
|
} else {
|
||||||
|
// When order = false ACK messages are sent in go routines so ackInChan cannot be closed until all goroutines done
|
||||||
|
ackInChan = make(chan *PacketAndToken)
|
||||||
|
go func() { // go routine to copy from ackInChan to ackOutChan until stopped
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case a := <-ackInChan:
|
||||||
|
ackOutChan <- a
|
||||||
|
case <-stopAckCopy:
|
||||||
|
close(ackCopyStopped) // Signal main go routine that it is safe to close ackOutChan
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ackInChan: // drain ackInChan to ensure all goRoutines can complete cleanly (ACK dropped)
|
||||||
|
DEBUG.Println(ROU, "matchAndDispatch received acknowledgment after processing stopped (ACK dropped).")
|
||||||
|
case <-goRoutinesDone:
|
||||||
|
close(ackInChan) // Nothing further should be sent (a panic is probably better than silent failure)
|
||||||
|
DEBUG.Println(ROU, "matchAndDispatch order=false copy goroutine exiting.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() { // Main go routine handling inbound messages
|
||||||
for message := range messages {
|
for message := range messages {
|
||||||
// DEBUG.Println(ROU, "matchAndDispatch received message")
|
// DEBUG.Println(ROU, "matchAndDispatch received message")
|
||||||
sent := false
|
sent := false
|
||||||
r.RLock()
|
r.RLock()
|
||||||
m := messageFromPublish(message, ackFunc(ackChan, client.persist, message))
|
m := messageFromPublish(message, ackFunc(ackInChan, client.persist, message))
|
||||||
var handlers []MessageHandler
|
var handlers []MessageHandler
|
||||||
for e := r.routes.Front(); e != nil; e = e.Next() {
|
for e := r.routes.Front(); e != nil; e = e.Next() {
|
||||||
if e.Value.(*route).match(message.TopicName) {
|
if e.Value.(*route).match(message.TopicName) {
|
||||||
@@ -146,9 +179,11 @@ func (r *router) matchAndDispatch(messages <-chan *packets.PublishPacket, order
|
|||||||
handlers = append(handlers, e.Value.(*route).callback)
|
handlers = append(handlers, e.Value.(*route).callback)
|
||||||
} else {
|
} else {
|
||||||
hd := e.Value.(*route).callback
|
hd := e.Value.(*route).callback
|
||||||
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
hd(client, m)
|
hd(client, m)
|
||||||
m.Ack()
|
m.Ack()
|
||||||
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
sent = true
|
sent = true
|
||||||
@@ -159,9 +194,11 @@ func (r *router) matchAndDispatch(messages <-chan *packets.PublishPacket, order
|
|||||||
if order {
|
if order {
|
||||||
handlers = append(handlers, r.defaultHandler)
|
handlers = append(handlers, r.defaultHandler)
|
||||||
} else {
|
} else {
|
||||||
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
r.defaultHandler(client, m)
|
r.defaultHandler(client, m)
|
||||||
m.Ack()
|
m.Ack()
|
||||||
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -175,8 +212,18 @@ func (r *router) matchAndDispatch(messages <-chan *packets.PublishPacket, order
|
|||||||
}
|
}
|
||||||
// DEBUG.Println(ROU, "matchAndDispatch handled message")
|
// DEBUG.Println(ROU, "matchAndDispatch handled message")
|
||||||
}
|
}
|
||||||
close(ackChan)
|
if order {
|
||||||
|
close(ackOutChan)
|
||||||
|
} else { // Ensure that nothing further will be written to ackOutChan before closing it
|
||||||
|
close(stopAckCopy)
|
||||||
|
<-ackCopyStopped
|
||||||
|
close(ackOutChan)
|
||||||
|
go func() {
|
||||||
|
wg.Wait() // Note: If this remains running then the user has handlers that are not returning
|
||||||
|
close(goRoutinesDone)
|
||||||
|
}()
|
||||||
|
}
|
||||||
DEBUG.Println(ROU, "matchAndDispatch exiting")
|
DEBUG.Println(ROU, "matchAndDispatch exiting")
|
||||||
}()
|
}()
|
||||||
return ackChan
|
return ackOutChan
|
||||||
}
|
}
|
||||||
|
|||||||
16
vendor/github.com/eclipse/paho.mqtt.golang/websocket.go
generated
vendored
16
vendor/github.com/eclipse/paho.mqtt.golang/websocket.go
generated
vendored
@@ -2,9 +2,11 @@ package mqtt
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -15,8 +17,11 @@ import (
|
|||||||
type WebsocketOptions struct {
|
type WebsocketOptions struct {
|
||||||
ReadBufferSize int
|
ReadBufferSize int
|
||||||
WriteBufferSize int
|
WriteBufferSize int
|
||||||
|
Proxy ProxyFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProxyFunction func(req *http.Request) (*url.URL, error)
|
||||||
|
|
||||||
// NewWebsocket returns a new websocket and returns a net.Conn compatible interface using the gorilla/websocket package
|
// NewWebsocket returns a new websocket and returns a net.Conn compatible interface using the gorilla/websocket package
|
||||||
func NewWebsocket(host string, tlsc *tls.Config, timeout time.Duration, requestHeader http.Header, options *WebsocketOptions) (net.Conn, error) {
|
func NewWebsocket(host string, tlsc *tls.Config, timeout time.Duration, requestHeader http.Header, options *WebsocketOptions) (net.Conn, error) {
|
||||||
if timeout == 0 {
|
if timeout == 0 {
|
||||||
@@ -27,9 +32,11 @@ func NewWebsocket(host string, tlsc *tls.Config, timeout time.Duration, requestH
|
|||||||
// Apply default options
|
// Apply default options
|
||||||
options = &WebsocketOptions{}
|
options = &WebsocketOptions{}
|
||||||
}
|
}
|
||||||
|
if options.Proxy == nil {
|
||||||
|
options.Proxy = http.ProxyFromEnvironment
|
||||||
|
}
|
||||||
dialer := &websocket.Dialer{
|
dialer := &websocket.Dialer{
|
||||||
Proxy: http.ProxyFromEnvironment,
|
Proxy: options.Proxy,
|
||||||
HandshakeTimeout: timeout,
|
HandshakeTimeout: timeout,
|
||||||
EnableCompression: false,
|
EnableCompression: false,
|
||||||
TLSClientConfig: tlsc,
|
TLSClientConfig: tlsc,
|
||||||
@@ -38,9 +45,12 @@ func NewWebsocket(host string, tlsc *tls.Config, timeout time.Duration, requestH
|
|||||||
WriteBufferSize: options.WriteBufferSize,
|
WriteBufferSize: options.WriteBufferSize,
|
||||||
}
|
}
|
||||||
|
|
||||||
ws, _, err := dialer.Dial(host, requestHeader)
|
ws, resp, err := dialer.Dial(host, requestHeader)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if resp != nil {
|
||||||
|
WARN.Println(CLI, fmt.Sprintf("Websocket handshake failure. StatusCode: %d. Body: %s", resp.StatusCode, resp.Body))
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
10
vendor/github.com/golang/protobuf/proto/registry.go
generated
vendored
10
vendor/github.com/golang/protobuf/proto/registry.go
generated
vendored
@@ -13,6 +13,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/reflect/protodesc"
|
||||||
"google.golang.org/protobuf/reflect/protoreflect"
|
"google.golang.org/protobuf/reflect/protoreflect"
|
||||||
"google.golang.org/protobuf/reflect/protoregistry"
|
"google.golang.org/protobuf/reflect/protoregistry"
|
||||||
"google.golang.org/protobuf/runtime/protoimpl"
|
"google.golang.org/protobuf/runtime/protoimpl"
|
||||||
@@ -62,14 +63,7 @@ func FileDescriptor(s filePath) fileDescGZIP {
|
|||||||
// Find the descriptor in the v2 registry.
|
// Find the descriptor in the v2 registry.
|
||||||
var b []byte
|
var b []byte
|
||||||
if fd, _ := protoregistry.GlobalFiles.FindFileByPath(s); fd != nil {
|
if fd, _ := protoregistry.GlobalFiles.FindFileByPath(s); fd != nil {
|
||||||
if fd, ok := fd.(interface{ ProtoLegacyRawDesc() []byte }); ok {
|
b, _ = Marshal(protodesc.ToFileDescriptorProto(fd))
|
||||||
b = fd.ProtoLegacyRawDesc()
|
|
||||||
} else {
|
|
||||||
// TODO: Use protodesc.ToFileDescriptorProto to construct
|
|
||||||
// a descriptorpb.FileDescriptorProto and marshal it.
|
|
||||||
// However, doing so causes the proto package to have a dependency
|
|
||||||
// on descriptorpb, leading to cyclic dependency issues.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locally cache the raw descriptor form for the file.
|
// Locally cache the raw descriptor form for the file.
|
||||||
|
|||||||
3
vendor/github.com/gorilla/websocket/go.mod
generated
vendored
3
vendor/github.com/gorilla/websocket/go.mod
generated
vendored
@@ -1,3 +0,0 @@
|
|||||||
module github.com/gorilla/websocket
|
|
||||||
|
|
||||||
go 1.12
|
|
||||||
0
vendor/github.com/gorilla/websocket/go.sum
generated
vendored
0
vendor/github.com/gorilla/websocket/go.sum
generated
vendored
4
vendor/github.com/sirupsen/logrus/.gitignore
generated
vendored
4
vendor/github.com/sirupsen/logrus/.gitignore
generated
vendored
@@ -1,4 +0,0 @@
|
|||||||
logrus
|
|
||||||
vendor
|
|
||||||
|
|
||||||
.idea/
|
|
||||||
40
vendor/github.com/sirupsen/logrus/.golangci.yml
generated
vendored
40
vendor/github.com/sirupsen/logrus/.golangci.yml
generated
vendored
@@ -1,40 +0,0 @@
|
|||||||
run:
|
|
||||||
# do not run on test files yet
|
|
||||||
tests: false
|
|
||||||
|
|
||||||
# all available settings of specific linters
|
|
||||||
linters-settings:
|
|
||||||
errcheck:
|
|
||||||
# report about not checking of errors in type assetions: `a := b.(MyStruct)`;
|
|
||||||
# default is false: such cases aren't reported by default.
|
|
||||||
check-type-assertions: false
|
|
||||||
|
|
||||||
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
|
|
||||||
# default is false: such cases aren't reported by default.
|
|
||||||
check-blank: false
|
|
||||||
|
|
||||||
lll:
|
|
||||||
line-length: 100
|
|
||||||
tab-width: 4
|
|
||||||
|
|
||||||
prealloc:
|
|
||||||
simple: false
|
|
||||||
range-loops: false
|
|
||||||
for-loops: false
|
|
||||||
|
|
||||||
whitespace:
|
|
||||||
multi-if: false # Enforces newlines (or comments) after every multi-line if statement
|
|
||||||
multi-func: false # Enforces newlines (or comments) after every multi-line function signature
|
|
||||||
|
|
||||||
linters:
|
|
||||||
enable:
|
|
||||||
- megacheck
|
|
||||||
- govet
|
|
||||||
disable:
|
|
||||||
- maligned
|
|
||||||
- prealloc
|
|
||||||
disable-all: false
|
|
||||||
presets:
|
|
||||||
- bugs
|
|
||||||
- unused
|
|
||||||
fast: false
|
|
||||||
17
vendor/github.com/sirupsen/logrus/.travis.yml
generated
vendored
17
vendor/github.com/sirupsen/logrus/.travis.yml
generated
vendored
@@ -1,17 +0,0 @@
|
|||||||
language: go
|
|
||||||
go_import_path: github.com/sirupsen/logrus
|
|
||||||
git:
|
|
||||||
depth: 1
|
|
||||||
env:
|
|
||||||
- GO111MODULE=on
|
|
||||||
go: [1.13.x, 1.14.x]
|
|
||||||
os: [linux, osx]
|
|
||||||
install:
|
|
||||||
- ./travis/install.sh
|
|
||||||
script:
|
|
||||||
- ./travis/cross_build.sh
|
|
||||||
- ./travis/lint.sh
|
|
||||||
- export GOMAXPROCS=4
|
|
||||||
- export GORACE=halt_on_error=1
|
|
||||||
- go test -race -v ./...
|
|
||||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then go test -race -v -tags appengine ./... ; fi
|
|
||||||
223
vendor/github.com/sirupsen/logrus/CHANGELOG.md
generated
vendored
223
vendor/github.com/sirupsen/logrus/CHANGELOG.md
generated
vendored
@@ -1,223 +0,0 @@
|
|||||||
# 1.6.0
|
|
||||||
Fixes:
|
|
||||||
* end of line cleanup
|
|
||||||
* revert the entry concurrency bug fix whic leads to deadlock under some circumstances
|
|
||||||
* update dependency on go-windows-terminal-sequences to fix a crash with go 1.14
|
|
||||||
|
|
||||||
Features:
|
|
||||||
* add an option to the `TextFormatter` to completely disable fields quoting
|
|
||||||
|
|
||||||
# 1.5.0
|
|
||||||
Code quality:
|
|
||||||
* add golangci linter run on travis
|
|
||||||
|
|
||||||
Fixes:
|
|
||||||
* add mutex for hooks concurrent access on `Entry` data
|
|
||||||
* caller function field for go1.14
|
|
||||||
* fix build issue for gopherjs target
|
|
||||||
|
|
||||||
Feature:
|
|
||||||
* add an hooks/writer sub-package whose goal is to split output on different stream depending on the trace level
|
|
||||||
* add a `DisableHTMLEscape` option in the `JSONFormatter`
|
|
||||||
* add `ForceQuote` and `PadLevelText` options in the `TextFormatter`
|
|
||||||
|
|
||||||
# 1.4.2
|
|
||||||
* Fixes build break for plan9, nacl, solaris
|
|
||||||
# 1.4.1
|
|
||||||
This new release introduces:
|
|
||||||
* Enhance TextFormatter to not print caller information when they are empty (#944)
|
|
||||||
* Remove dependency on golang.org/x/crypto (#932, #943)
|
|
||||||
|
|
||||||
Fixes:
|
|
||||||
* Fix Entry.WithContext method to return a copy of the initial entry (#941)
|
|
||||||
|
|
||||||
# 1.4.0
|
|
||||||
This new release introduces:
|
|
||||||
* Add `DeferExitHandler`, similar to `RegisterExitHandler` but prepending the handler to the list of handlers (semantically like `defer`) (#848).
|
|
||||||
* Add `CallerPrettyfier` to `JSONFormatter` and `TextFormatter` (#909, #911)
|
|
||||||
* Add `Entry.WithContext()` and `Entry.Context`, to set a context on entries to be used e.g. in hooks (#919).
|
|
||||||
|
|
||||||
Fixes:
|
|
||||||
* Fix wrong method calls `Logger.Print` and `Logger.Warningln` (#893).
|
|
||||||
* Update `Entry.Logf` to not do string formatting unless the log level is enabled (#903)
|
|
||||||
* Fix infinite recursion on unknown `Level.String()` (#907)
|
|
||||||
* Fix race condition in `getCaller` (#916).
|
|
||||||
|
|
||||||
|
|
||||||
# 1.3.0
|
|
||||||
This new release introduces:
|
|
||||||
* Log, Logf, Logln functions for Logger and Entry that take a Level
|
|
||||||
|
|
||||||
Fixes:
|
|
||||||
* Building prometheus node_exporter on AIX (#840)
|
|
||||||
* Race condition in TextFormatter (#468)
|
|
||||||
* Travis CI import path (#868)
|
|
||||||
* Remove coloured output on Windows (#862)
|
|
||||||
* Pointer to func as field in JSONFormatter (#870)
|
|
||||||
* Properly marshal Levels (#873)
|
|
||||||
|
|
||||||
# 1.2.0
|
|
||||||
This new release introduces:
|
|
||||||
* A new method `SetReportCaller` in the `Logger` to enable the file, line and calling function from which the trace has been issued
|
|
||||||
* A new trace level named `Trace` whose level is below `Debug`
|
|
||||||
* A configurable exit function to be called upon a Fatal trace
|
|
||||||
* The `Level` object now implements `encoding.TextUnmarshaler` interface
|
|
||||||
|
|
||||||
# 1.1.1
|
|
||||||
This is a bug fix release.
|
|
||||||
* fix the build break on Solaris
|
|
||||||
* don't drop a whole trace in JSONFormatter when a field param is a function pointer which can not be serialized
|
|
||||||
|
|
||||||
# 1.1.0
|
|
||||||
This new release introduces:
|
|
||||||
* several fixes:
|
|
||||||
* a fix for a race condition on entry formatting
|
|
||||||
* proper cleanup of previously used entries before putting them back in the pool
|
|
||||||
* the extra new line at the end of message in text formatter has been removed
|
|
||||||
* a new global public API to check if a level is activated: IsLevelEnabled
|
|
||||||
* the following methods have been added to the Logger object
|
|
||||||
* IsLevelEnabled
|
|
||||||
* SetFormatter
|
|
||||||
* SetOutput
|
|
||||||
* ReplaceHooks
|
|
||||||
* introduction of go module
|
|
||||||
* an indent configuration for the json formatter
|
|
||||||
* output colour support for windows
|
|
||||||
* the field sort function is now configurable for text formatter
|
|
||||||
* the CLICOLOR and CLICOLOR\_FORCE environment variable support in text formater
|
|
||||||
|
|
||||||
# 1.0.6
|
|
||||||
|
|
||||||
This new release introduces:
|
|
||||||
* a new api WithTime which allows to easily force the time of the log entry
|
|
||||||
which is mostly useful for logger wrapper
|
|
||||||
* a fix reverting the immutability of the entry given as parameter to the hooks
|
|
||||||
a new configuration field of the json formatter in order to put all the fields
|
|
||||||
in a nested dictionnary
|
|
||||||
* a new SetOutput method in the Logger
|
|
||||||
* a new configuration of the textformatter to configure the name of the default keys
|
|
||||||
* a new configuration of the text formatter to disable the level truncation
|
|
||||||
|
|
||||||
# 1.0.5
|
|
||||||
|
|
||||||
* Fix hooks race (#707)
|
|
||||||
* Fix panic deadlock (#695)
|
|
||||||
|
|
||||||
# 1.0.4
|
|
||||||
|
|
||||||
* Fix race when adding hooks (#612)
|
|
||||||
* Fix terminal check in AppEngine (#635)
|
|
||||||
|
|
||||||
# 1.0.3
|
|
||||||
|
|
||||||
* Replace example files with testable examples
|
|
||||||
|
|
||||||
# 1.0.2
|
|
||||||
|
|
||||||
* bug: quote non-string values in text formatter (#583)
|
|
||||||
* Make (*Logger) SetLevel a public method
|
|
||||||
|
|
||||||
# 1.0.1
|
|
||||||
|
|
||||||
* bug: fix escaping in text formatter (#575)
|
|
||||||
|
|
||||||
# 1.0.0
|
|
||||||
|
|
||||||
* Officially changed name to lower-case
|
|
||||||
* bug: colors on Windows 10 (#541)
|
|
||||||
* bug: fix race in accessing level (#512)
|
|
||||||
|
|
||||||
# 0.11.5
|
|
||||||
|
|
||||||
* feature: add writer and writerlevel to entry (#372)
|
|
||||||
|
|
||||||
# 0.11.4
|
|
||||||
|
|
||||||
* bug: fix undefined variable on solaris (#493)
|
|
||||||
|
|
||||||
# 0.11.3
|
|
||||||
|
|
||||||
* formatter: configure quoting of empty values (#484)
|
|
||||||
* formatter: configure quoting character (default is `"`) (#484)
|
|
||||||
* bug: fix not importing io correctly in non-linux environments (#481)
|
|
||||||
|
|
||||||
# 0.11.2
|
|
||||||
|
|
||||||
* bug: fix windows terminal detection (#476)
|
|
||||||
|
|
||||||
# 0.11.1
|
|
||||||
|
|
||||||
* bug: fix tty detection with custom out (#471)
|
|
||||||
|
|
||||||
# 0.11.0
|
|
||||||
|
|
||||||
* performance: Use bufferpool to allocate (#370)
|
|
||||||
* terminal: terminal detection for app-engine (#343)
|
|
||||||
* feature: exit handler (#375)
|
|
||||||
|
|
||||||
# 0.10.0
|
|
||||||
|
|
||||||
* feature: Add a test hook (#180)
|
|
||||||
* feature: `ParseLevel` is now case-insensitive (#326)
|
|
||||||
* feature: `FieldLogger` interface that generalizes `Logger` and `Entry` (#308)
|
|
||||||
* performance: avoid re-allocations on `WithFields` (#335)
|
|
||||||
|
|
||||||
# 0.9.0
|
|
||||||
|
|
||||||
* logrus/text_formatter: don't emit empty msg
|
|
||||||
* logrus/hooks/airbrake: move out of main repository
|
|
||||||
* logrus/hooks/sentry: move out of main repository
|
|
||||||
* logrus/hooks/papertrail: move out of main repository
|
|
||||||
* logrus/hooks/bugsnag: move out of main repository
|
|
||||||
* logrus/core: run tests with `-race`
|
|
||||||
* logrus/core: detect TTY based on `stderr`
|
|
||||||
* logrus/core: support `WithError` on logger
|
|
||||||
* logrus/core: Solaris support
|
|
||||||
|
|
||||||
# 0.8.7
|
|
||||||
|
|
||||||
* logrus/core: fix possible race (#216)
|
|
||||||
* logrus/doc: small typo fixes and doc improvements
|
|
||||||
|
|
||||||
|
|
||||||
# 0.8.6
|
|
||||||
|
|
||||||
* hooks/raven: allow passing an initialized client
|
|
||||||
|
|
||||||
# 0.8.5
|
|
||||||
|
|
||||||
* logrus/core: revert #208
|
|
||||||
|
|
||||||
# 0.8.4
|
|
||||||
|
|
||||||
* formatter/text: fix data race (#218)
|
|
||||||
|
|
||||||
# 0.8.3
|
|
||||||
|
|
||||||
* logrus/core: fix entry log level (#208)
|
|
||||||
* logrus/core: improve performance of text formatter by 40%
|
|
||||||
* logrus/core: expose `LevelHooks` type
|
|
||||||
* logrus/core: add support for DragonflyBSD and NetBSD
|
|
||||||
* formatter/text: print structs more verbosely
|
|
||||||
|
|
||||||
# 0.8.2
|
|
||||||
|
|
||||||
* logrus: fix more Fatal family functions
|
|
||||||
|
|
||||||
# 0.8.1
|
|
||||||
|
|
||||||
* logrus: fix not exiting on `Fatalf` and `Fatalln`
|
|
||||||
|
|
||||||
# 0.8.0
|
|
||||||
|
|
||||||
* logrus: defaults to stderr instead of stdout
|
|
||||||
* hooks/sentry: add special field for `*http.Request`
|
|
||||||
* formatter/text: ignore Windows for colors
|
|
||||||
|
|
||||||
# 0.7.3
|
|
||||||
|
|
||||||
* formatter/\*: allow configuration of timestamp layout
|
|
||||||
|
|
||||||
# 0.7.2
|
|
||||||
|
|
||||||
* formatter/text: Add configuration option for time format (#158)
|
|
||||||
513
vendor/github.com/sirupsen/logrus/README.md
generated
vendored
513
vendor/github.com/sirupsen/logrus/README.md
generated
vendored
@@ -1,513 +0,0 @@
|
|||||||
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [](https://travis-ci.org/sirupsen/logrus) [](https://godoc.org/github.com/sirupsen/logrus)
|
|
||||||
|
|
||||||
Logrus is a structured logger for Go (golang), completely API compatible with
|
|
||||||
the standard library logger.
|
|
||||||
|
|
||||||
**Logrus is in maintenance-mode.** We will not be introducing new features. It's
|
|
||||||
simply too hard to do in a way that won't break many people's projects, which is
|
|
||||||
the last thing you want from your Logging library (again...).
|
|
||||||
|
|
||||||
This does not mean Logrus is dead. Logrus will continue to be maintained for
|
|
||||||
security, (backwards compatible) bug fixes, and performance (where we are
|
|
||||||
limited by the interface).
|
|
||||||
|
|
||||||
I believe Logrus' biggest contribution is to have played a part in today's
|
|
||||||
widespread use of structured logging in Golang. There doesn't seem to be a
|
|
||||||
reason to do a major, breaking iteration into Logrus V2, since the fantastic Go
|
|
||||||
community has built those independently. Many fantastic alternatives have sprung
|
|
||||||
up. Logrus would look like those, had it been re-designed with what we know
|
|
||||||
about structured logging in Go today. Check out, for example,
|
|
||||||
[Zerolog][zerolog], [Zap][zap], and [Apex][apex].
|
|
||||||
|
|
||||||
[zerolog]: https://github.com/rs/zerolog
|
|
||||||
[zap]: https://github.com/uber-go/zap
|
|
||||||
[apex]: https://github.com/apex/log
|
|
||||||
|
|
||||||
**Seeing weird case-sensitive problems?** It's in the past been possible to
|
|
||||||
import Logrus as both upper- and lower-case. Due to the Go package environment,
|
|
||||||
this caused issues in the community and we needed a standard. Some environments
|
|
||||||
experienced problems with the upper-case variant, so the lower-case was decided.
|
|
||||||
Everything using `logrus` will need to use the lower-case:
|
|
||||||
`github.com/sirupsen/logrus`. Any package that isn't, should be changed.
|
|
||||||
|
|
||||||
To fix Glide, see [these
|
|
||||||
comments](https://github.com/sirupsen/logrus/issues/553#issuecomment-306591437).
|
|
||||||
For an in-depth explanation of the casing issue, see [this
|
|
||||||
comment](https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276).
|
|
||||||
|
|
||||||
Nicely color-coded in development (when a TTY is attached, otherwise just
|
|
||||||
plain text):
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
With `log.SetFormatter(&log.JSONFormatter{})`, for easy parsing by logstash
|
|
||||||
or Splunk:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
|
|
||||||
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
|
|
||||||
|
|
||||||
{"level":"warning","msg":"The group's number increased tremendously!",
|
|
||||||
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
|
|
||||||
|
|
||||||
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
|
|
||||||
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
|
|
||||||
|
|
||||||
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
|
|
||||||
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
|
|
||||||
|
|
||||||
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
|
|
||||||
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
|
|
||||||
```
|
|
||||||
|
|
||||||
With the default `log.SetFormatter(&log.TextFormatter{})` when a TTY is not
|
|
||||||
attached, the output is compatible with the
|
|
||||||
[logfmt](http://godoc.org/github.com/kr/logfmt) format:
|
|
||||||
|
|
||||||
```text
|
|
||||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
|
|
||||||
time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
|
|
||||||
time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
|
|
||||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
|
|
||||||
time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
|
|
||||||
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
|
|
||||||
```
|
|
||||||
To ensure this behaviour even if a TTY is attached, set your formatter as follows:
|
|
||||||
|
|
||||||
```go
|
|
||||||
log.SetFormatter(&log.TextFormatter{
|
|
||||||
DisableColors: true,
|
|
||||||
FullTimestamp: true,
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Logging Method Name
|
|
||||||
|
|
||||||
If you wish to add the calling method as a field, instruct the logger via:
|
|
||||||
```go
|
|
||||||
log.SetReportCaller(true)
|
|
||||||
```
|
|
||||||
This adds the caller as 'method' like so:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{"animal":"penguin","level":"fatal","method":"github.com/sirupsen/arcticcreatures.migrate","msg":"a penguin swims by",
|
|
||||||
"time":"2014-03-10 19:57:38.562543129 -0400 EDT"}
|
|
||||||
```
|
|
||||||
|
|
||||||
```text
|
|
||||||
time="2015-03-26T01:27:38-04:00" level=fatal method=github.com/sirupsen/arcticcreatures.migrate msg="a penguin swims by" animal=penguin
|
|
||||||
```
|
|
||||||
Note that this does add measurable overhead - the cost will depend on the version of Go, but is
|
|
||||||
between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your
|
|
||||||
environment via benchmarks:
|
|
||||||
```
|
|
||||||
go test -bench=.*CallerTracing
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### Case-sensitivity
|
|
||||||
|
|
||||||
The organization's name was changed to lower-case--and this will not be changed
|
|
||||||
back. If you are getting import conflicts due to case sensitivity, please use
|
|
||||||
the lower-case import: `github.com/sirupsen/logrus`.
|
|
||||||
|
|
||||||
#### Example
|
|
||||||
|
|
||||||
The simplest way to use Logrus is simply the package-level exported logger:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"animal": "walrus",
|
|
||||||
}).Info("A walrus appears")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that it's completely api-compatible with the stdlib logger, so you can
|
|
||||||
replace your `log` imports everywhere with `log "github.com/sirupsen/logrus"`
|
|
||||||
and you'll now have the flexibility of Logrus. You can customize it all you
|
|
||||||
want:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Log as JSON instead of the default ASCII formatter.
|
|
||||||
log.SetFormatter(&log.JSONFormatter{})
|
|
||||||
|
|
||||||
// Output to stdout instead of the default stderr
|
|
||||||
// Can be any io.Writer, see below for File example
|
|
||||||
log.SetOutput(os.Stdout)
|
|
||||||
|
|
||||||
// Only log the warning severity or above.
|
|
||||||
log.SetLevel(log.WarnLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"animal": "walrus",
|
|
||||||
"size": 10,
|
|
||||||
}).Info("A group of walrus emerges from the ocean")
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"omg": true,
|
|
||||||
"number": 122,
|
|
||||||
}).Warn("The group's number increased tremendously!")
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"omg": true,
|
|
||||||
"number": 100,
|
|
||||||
}).Fatal("The ice breaks!")
|
|
||||||
|
|
||||||
// A common pattern is to re-use fields between logging statements by re-using
|
|
||||||
// the logrus.Entry returned from WithFields()
|
|
||||||
contextLogger := log.WithFields(log.Fields{
|
|
||||||
"common": "this is a common field",
|
|
||||||
"other": "I also should be logged always",
|
|
||||||
})
|
|
||||||
|
|
||||||
contextLogger.Info("I'll be logged with common and other field")
|
|
||||||
contextLogger.Info("Me too")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
For more advanced usage such as logging to multiple locations from the same
|
|
||||||
application, you can also create an instance of the `logrus` Logger:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Create a new instance of the logger. You can have any number of instances.
|
|
||||||
var log = logrus.New()
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// The API for setting attributes is a little different than the package level
|
|
||||||
// exported logger. See Godoc.
|
|
||||||
log.Out = os.Stdout
|
|
||||||
|
|
||||||
// You could set this to any `io.Writer` such as a file
|
|
||||||
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
|
||||||
// if err == nil {
|
|
||||||
// log.Out = file
|
|
||||||
// } else {
|
|
||||||
// log.Info("Failed to log to file, using default stderr")
|
|
||||||
// }
|
|
||||||
|
|
||||||
log.WithFields(logrus.Fields{
|
|
||||||
"animal": "walrus",
|
|
||||||
"size": 10,
|
|
||||||
}).Info("A group of walrus emerges from the ocean")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Fields
|
|
||||||
|
|
||||||
Logrus encourages careful, structured logging through logging fields instead of
|
|
||||||
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
|
|
||||||
to send event %s to topic %s with key %d")`, you should log the much more
|
|
||||||
discoverable:
|
|
||||||
|
|
||||||
```go
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"event": event,
|
|
||||||
"topic": topic,
|
|
||||||
"key": key,
|
|
||||||
}).Fatal("Failed to send event")
|
|
||||||
```
|
|
||||||
|
|
||||||
We've found this API forces you to think about logging in a way that produces
|
|
||||||
much more useful logging messages. We've been in countless situations where just
|
|
||||||
a single added field to a log statement that was already there would've saved us
|
|
||||||
hours. The `WithFields` call is optional.
|
|
||||||
|
|
||||||
In general, with Logrus using any of the `printf`-family functions should be
|
|
||||||
seen as a hint you should add a field, however, you can still use the
|
|
||||||
`printf`-family functions with Logrus.
|
|
||||||
|
|
||||||
#### Default Fields
|
|
||||||
|
|
||||||
Often it's helpful to have fields _always_ attached to log statements in an
|
|
||||||
application or parts of one. For example, you may want to always log the
|
|
||||||
`request_id` and `user_ip` in the context of a request. Instead of writing
|
|
||||||
`log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})` on
|
|
||||||
every line, you can create a `logrus.Entry` to pass around instead:
|
|
||||||
|
|
||||||
```go
|
|
||||||
requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
|
|
||||||
requestLogger.Info("something happened on that request") # will log request_id and user_ip
|
|
||||||
requestLogger.Warn("something not great happened")
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Hooks
|
|
||||||
|
|
||||||
You can add hooks for logging levels. For example to send errors to an exception
|
|
||||||
tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
|
|
||||||
multiple places simultaneously, e.g. syslog.
|
|
||||||
|
|
||||||
Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
|
|
||||||
`init`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake"
|
|
||||||
logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
|
|
||||||
"log/syslog"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
|
|
||||||
// Use the Airbrake hook to report errors that have Error severity or above to
|
|
||||||
// an exception tracker. You can create custom hooks, see the Hooks section.
|
|
||||||
log.AddHook(airbrake.NewHook(123, "xyz", "production"))
|
|
||||||
|
|
||||||
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Unable to connect to local syslog daemon")
|
|
||||||
} else {
|
|
||||||
log.AddHook(hook)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md).
|
|
||||||
|
|
||||||
A list of currently known service hooks can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks)
|
|
||||||
|
|
||||||
|
|
||||||
#### Level logging
|
|
||||||
|
|
||||||
Logrus has seven logging levels: Trace, Debug, Info, Warning, Error, Fatal and Panic.
|
|
||||||
|
|
||||||
```go
|
|
||||||
log.Trace("Something very low level.")
|
|
||||||
log.Debug("Useful debugging information.")
|
|
||||||
log.Info("Something noteworthy happened!")
|
|
||||||
log.Warn("You should probably take a look at this.")
|
|
||||||
log.Error("Something failed but I'm not quitting.")
|
|
||||||
// Calls os.Exit(1) after logging
|
|
||||||
log.Fatal("Bye.")
|
|
||||||
// Calls panic() after logging
|
|
||||||
log.Panic("I'm bailing.")
|
|
||||||
```
|
|
||||||
|
|
||||||
You can set the logging level on a `Logger`, then it will only log entries with
|
|
||||||
that severity or anything above it:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Will log anything that is info or above (warn, error, fatal, panic). Default.
|
|
||||||
log.SetLevel(log.InfoLevel)
|
|
||||||
```
|
|
||||||
|
|
||||||
It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
|
|
||||||
environment if your application has that.
|
|
||||||
|
|
||||||
#### Entries
|
|
||||||
|
|
||||||
Besides the fields added with `WithField` or `WithFields` some fields are
|
|
||||||
automatically added to all logging events:
|
|
||||||
|
|
||||||
1. `time`. The timestamp when the entry was created.
|
|
||||||
2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after
|
|
||||||
the `AddFields` call. E.g. `Failed to send event.`
|
|
||||||
3. `level`. The logging level. E.g. `info`.
|
|
||||||
|
|
||||||
#### Environments
|
|
||||||
|
|
||||||
Logrus has no notion of environment.
|
|
||||||
|
|
||||||
If you wish for hooks and formatters to only be used in specific environments,
|
|
||||||
you should handle that yourself. For example, if your application has a global
|
|
||||||
variable `Environment`, which is a string representation of the environment you
|
|
||||||
could do:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
init() {
|
|
||||||
// do something here to set environment depending on an environment variable
|
|
||||||
// or command-line flag
|
|
||||||
if Environment == "production" {
|
|
||||||
log.SetFormatter(&log.JSONFormatter{})
|
|
||||||
} else {
|
|
||||||
// The TextFormatter is default, you don't actually have to do this.
|
|
||||||
log.SetFormatter(&log.TextFormatter{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This configuration is how `logrus` was intended to be used, but JSON in
|
|
||||||
production is mostly only useful if you do log aggregation with tools like
|
|
||||||
Splunk or Logstash.
|
|
||||||
|
|
||||||
#### Formatters
|
|
||||||
|
|
||||||
The built-in logging formatters are:
|
|
||||||
|
|
||||||
* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
|
|
||||||
without colors.
|
|
||||||
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
|
|
||||||
field to `true`. To force no colored output even if there is a TTY set the
|
|
||||||
`DisableColors` field to `true`. For Windows, see
|
|
||||||
[github.com/mattn/go-colorable](https://github.com/mattn/go-colorable).
|
|
||||||
* When colors are enabled, levels are truncated to 4 characters by default. To disable
|
|
||||||
truncation set the `DisableLevelTruncation` field to `true`.
|
|
||||||
* When outputting to a TTY, it's often helpful to visually scan down a column where all the levels are the same width. Setting the `PadLevelText` field to `true` enables this behavior, by adding padding to the level text.
|
|
||||||
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#TextFormatter).
|
|
||||||
* `logrus.JSONFormatter`. Logs fields as JSON.
|
|
||||||
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#JSONFormatter).
|
|
||||||
|
|
||||||
Third party logging formatters:
|
|
||||||
|
|
||||||
* [`FluentdFormatter`](https://github.com/joonix/log). Formats entries that can be parsed by Kubernetes and Google Container Engine.
|
|
||||||
* [`GELF`](https://github.com/fabienm/go-logrus-formatters). Formats entries so they comply to Graylog's [GELF 1.1 specification](http://docs.graylog.org/en/2.4/pages/gelf.html).
|
|
||||||
* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events.
|
|
||||||
* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
|
|
||||||
* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the Power of Zalgo.
|
|
||||||
* [`nested-logrus-formatter`](https://github.com/antonfisher/nested-logrus-formatter). Converts logrus fields to a nested structure.
|
|
||||||
* [`powerful-logrus-formatter`](https://github.com/zput/zxcTool). get fileName, log's line number and the latest function's name when print log; Sava log to files.
|
|
||||||
* [`caption-json-formatter`](https://github.com/nolleh/caption_json_formatter). logrus's message json formatter with human-readable caption added.
|
|
||||||
|
|
||||||
You can define your formatter by implementing the `Formatter` interface,
|
|
||||||
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
|
|
||||||
`Fields` type (`map[string]interface{}`) with all your fields as well as the
|
|
||||||
default ones (see Entries section above):
|
|
||||||
|
|
||||||
```go
|
|
||||||
type MyJSONFormatter struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
log.SetFormatter(new(MyJSONFormatter))
|
|
||||||
|
|
||||||
func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) {
|
|
||||||
// Note this doesn't include Time, Level and Message which are available on
|
|
||||||
// the Entry. Consult `godoc` on information about those fields or read the
|
|
||||||
// source of the official loggers.
|
|
||||||
serialized, err := json.Marshal(entry.Data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
|
||||||
}
|
|
||||||
return append(serialized, '\n'), nil
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Logger as an `io.Writer`
|
|
||||||
|
|
||||||
Logrus can be transformed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it.
|
|
||||||
|
|
||||||
```go
|
|
||||||
w := logger.Writer()
|
|
||||||
defer w.Close()
|
|
||||||
|
|
||||||
srv := http.Server{
|
|
||||||
// create a stdlib log.Logger that writes to
|
|
||||||
// logrus.Logger.
|
|
||||||
ErrorLog: log.New(w, "", 0),
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Each line written to that writer will be printed the usual way, using formatters
|
|
||||||
and hooks. The level for those entries is `info`.
|
|
||||||
|
|
||||||
This means that we can override the standard library logger easily:
|
|
||||||
|
|
||||||
```go
|
|
||||||
logger := logrus.New()
|
|
||||||
logger.Formatter = &logrus.JSONFormatter{}
|
|
||||||
|
|
||||||
// Use logrus for standard log output
|
|
||||||
// Note that `log` here references stdlib's log
|
|
||||||
// Not logrus imported under the name `log`.
|
|
||||||
log.SetOutput(logger.Writer())
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Rotation
|
|
||||||
|
|
||||||
Log rotation is not provided with Logrus. Log rotation should be done by an
|
|
||||||
external program (like `logrotate(8)`) that can compress and delete old log
|
|
||||||
entries. It should not be a feature of the application-level logger.
|
|
||||||
|
|
||||||
#### Tools
|
|
||||||
|
|
||||||
| Tool | Description |
|
|
||||||
| ---- | ----------- |
|
|
||||||
|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will be generated with different configs in different environments.|
|
|
||||||
|[Logrus Viper Helper](https://github.com/heirko/go-contrib/tree/master/logrusHelper)|An Helper around Logrus to wrap with spf13/Viper to load configuration with fangs! And to simplify Logrus configuration use some behavior of [Logrus Mate](https://github.com/gogap/logrus_mate). [sample](https://github.com/heirko/iris-contrib/blob/master/middleware/logrus-logger/example) |
|
|
||||||
|
|
||||||
#### Testing
|
|
||||||
|
|
||||||
Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides:
|
|
||||||
|
|
||||||
* decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just adds the `test` hook
|
|
||||||
* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any):
|
|
||||||
|
|
||||||
```go
|
|
||||||
import(
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/sirupsen/logrus/hooks/test"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSomething(t*testing.T){
|
|
||||||
logger, hook := test.NewNullLogger()
|
|
||||||
logger.Error("Helloerror")
|
|
||||||
|
|
||||||
assert.Equal(t, 1, len(hook.Entries))
|
|
||||||
assert.Equal(t, logrus.ErrorLevel, hook.LastEntry().Level)
|
|
||||||
assert.Equal(t, "Helloerror", hook.LastEntry().Message)
|
|
||||||
|
|
||||||
hook.Reset()
|
|
||||||
assert.Nil(t, hook.LastEntry())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Fatal handlers
|
|
||||||
|
|
||||||
Logrus can register one or more functions that will be called when any `fatal`
|
|
||||||
level message is logged. The registered handlers will be executed before
|
|
||||||
logrus performs an `os.Exit(1)`. This behavior may be helpful if callers need
|
|
||||||
to gracefully shutdown. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted.
|
|
||||||
|
|
||||||
```
|
|
||||||
...
|
|
||||||
handler := func() {
|
|
||||||
// gracefully shutdown something...
|
|
||||||
}
|
|
||||||
logrus.RegisterExitHandler(handler)
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Thread safety
|
|
||||||
|
|
||||||
By default, Logger is protected by a mutex for concurrent writes. The mutex is held when calling hooks and writing logs.
|
|
||||||
If you are sure such locking is not needed, you can call logger.SetNoLock() to disable the locking.
|
|
||||||
|
|
||||||
Situation when locking is not needed includes:
|
|
||||||
|
|
||||||
* You have no hooks registered, or hooks calling is already thread-safe.
|
|
||||||
|
|
||||||
* Writing to logger.Out is already thread-safe, for example:
|
|
||||||
|
|
||||||
1) logger.Out is protected by locks.
|
|
||||||
|
|
||||||
2) logger.Out is an os.File handler opened with `O_APPEND` flag, and every write is smaller than 4k. (This allows multi-thread/multi-process writing)
|
|
||||||
|
|
||||||
(Refer to http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/)
|
|
||||||
76
vendor/github.com/sirupsen/logrus/alt_exit.go
generated
vendored
76
vendor/github.com/sirupsen/logrus/alt_exit.go
generated
vendored
@@ -1,76 +0,0 @@
|
|||||||
package logrus
|
|
||||||
|
|
||||||
// The following code was sourced and modified from the
|
|
||||||
// https://github.com/tebeka/atexit package governed by the following license:
|
|
||||||
//
|
|
||||||
// Copyright (c) 2012 Miki Tebeka <miki.tebeka@gmail.com>.
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
var handlers = []func(){}
|
|
||||||
|
|
||||||
func runHandler(handler func()) {
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, "Error: Logrus exit handler error:", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
handler()
|
|
||||||
}
|
|
||||||
|
|
||||||
func runHandlers() {
|
|
||||||
for _, handler := range handlers {
|
|
||||||
runHandler(handler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exit runs all the Logrus atexit handlers and then terminates the program using os.Exit(code)
|
|
||||||
func Exit(code int) {
|
|
||||||
runHandlers()
|
|
||||||
os.Exit(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterExitHandler appends a Logrus Exit handler to the list of handlers,
|
|
||||||
// call logrus.Exit to invoke all handlers. The handlers will also be invoked when
|
|
||||||
// any Fatal log entry is made.
|
|
||||||
//
|
|
||||||
// This method is useful when a caller wishes to use logrus to log a fatal
|
|
||||||
// message but also needs to gracefully shutdown. An example usecase could be
|
|
||||||
// closing database connections, or sending a alert that the application is
|
|
||||||
// closing.
|
|
||||||
func RegisterExitHandler(handler func()) {
|
|
||||||
handlers = append(handlers, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeferExitHandler prepends a Logrus Exit handler to the list of handlers,
|
|
||||||
// call logrus.Exit to invoke all handlers. The handlers will also be invoked when
|
|
||||||
// any Fatal log entry is made.
|
|
||||||
//
|
|
||||||
// This method is useful when a caller wishes to use logrus to log a fatal
|
|
||||||
// message but also needs to gracefully shutdown. An example usecase could be
|
|
||||||
// closing database connections, or sending a alert that the application is
|
|
||||||
// closing.
|
|
||||||
func DeferExitHandler(handler func()) {
|
|
||||||
handlers = append([]func(){handler}, handlers...)
|
|
||||||
}
|
|
||||||
14
vendor/github.com/sirupsen/logrus/appveyor.yml
generated
vendored
14
vendor/github.com/sirupsen/logrus/appveyor.yml
generated
vendored
@@ -1,14 +0,0 @@
|
|||||||
version: "{build}"
|
|
||||||
platform: x64
|
|
||||||
clone_folder: c:\gopath\src\github.com\sirupsen\logrus
|
|
||||||
environment:
|
|
||||||
GOPATH: c:\gopath
|
|
||||||
branches:
|
|
||||||
only:
|
|
||||||
- master
|
|
||||||
install:
|
|
||||||
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
|
|
||||||
- go version
|
|
||||||
build_script:
|
|
||||||
- go get -t
|
|
||||||
- go test
|
|
||||||
52
vendor/github.com/sirupsen/logrus/buffer_pool.go
generated
vendored
52
vendor/github.com/sirupsen/logrus/buffer_pool.go
generated
vendored
@@ -1,52 +0,0 @@
|
|||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
bufferPool BufferPool
|
|
||||||
)
|
|
||||||
|
|
||||||
type BufferPool interface {
|
|
||||||
Put(*bytes.Buffer)
|
|
||||||
Get() *bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
type defaultPool struct {
|
|
||||||
pool *sync.Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *defaultPool) Put(buf *bytes.Buffer) {
|
|
||||||
p.pool.Put(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *defaultPool) Get() *bytes.Buffer {
|
|
||||||
return p.pool.Get().(*bytes.Buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getBuffer() *bytes.Buffer {
|
|
||||||
return bufferPool.Get()
|
|
||||||
}
|
|
||||||
|
|
||||||
func putBuffer(buf *bytes.Buffer) {
|
|
||||||
buf.Reset()
|
|
||||||
bufferPool.Put(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBufferPool allows to replace the default logrus buffer pool
|
|
||||||
// to better meets the specific needs of an application.
|
|
||||||
func SetBufferPool(bp BufferPool) {
|
|
||||||
bufferPool = bp
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
SetBufferPool(&defaultPool{
|
|
||||||
pool: &sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
return new(bytes.Buffer)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
26
vendor/github.com/sirupsen/logrus/doc.go
generated
vendored
26
vendor/github.com/sirupsen/logrus/doc.go
generated
vendored
@@ -1,26 +0,0 @@
|
|||||||
/*
|
|
||||||
Package logrus is a structured logger for Go, completely API compatible with the standard library logger.
|
|
||||||
|
|
||||||
|
|
||||||
The simplest way to use Logrus is simply the package-level exported logger:
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"animal": "walrus",
|
|
||||||
"number": 1,
|
|
||||||
"size": 10,
|
|
||||||
}).Info("A walrus appears")
|
|
||||||
}
|
|
||||||
|
|
||||||
Output:
|
|
||||||
time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
|
|
||||||
|
|
||||||
For a full guide visit https://github.com/sirupsen/logrus
|
|
||||||
*/
|
|
||||||
package logrus
|
|
||||||
422
vendor/github.com/sirupsen/logrus/entry.go
generated
vendored
422
vendor/github.com/sirupsen/logrus/entry.go
generated
vendored
@@ -1,422 +0,0 @@
|
|||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
|
|
||||||
// qualified package name, cached at first use
|
|
||||||
logrusPackage string
|
|
||||||
|
|
||||||
// Positions in the call stack when tracing to report the calling method
|
|
||||||
minimumCallerDepth int
|
|
||||||
|
|
||||||
// Used for caller information initialisation
|
|
||||||
callerInitOnce sync.Once
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
maximumCallerDepth int = 25
|
|
||||||
knownLogrusFrames int = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// start at the bottom of the stack before the package-name cache is primed
|
|
||||||
minimumCallerDepth = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defines the key when adding errors using WithError.
|
|
||||||
var ErrorKey = "error"
|
|
||||||
|
|
||||||
// An entry is the final or intermediate Logrus logging entry. It contains all
|
|
||||||
// the fields passed with WithField{,s}. It's finally logged when Trace, Debug,
|
|
||||||
// Info, Warn, Error, Fatal or Panic is called on it. These objects can be
|
|
||||||
// reused and passed around as much as you wish to avoid field duplication.
|
|
||||||
type Entry struct {
|
|
||||||
Logger *Logger
|
|
||||||
|
|
||||||
// Contains all the fields set by the user.
|
|
||||||
Data Fields
|
|
||||||
|
|
||||||
// Time at which the log entry was created
|
|
||||||
Time time.Time
|
|
||||||
|
|
||||||
// Level the log entry was logged at: Trace, Debug, Info, Warn, Error, Fatal or Panic
|
|
||||||
// This field will be set on entry firing and the value will be equal to the one in Logger struct field.
|
|
||||||
Level Level
|
|
||||||
|
|
||||||
// Calling method, with package name
|
|
||||||
Caller *runtime.Frame
|
|
||||||
|
|
||||||
// Message passed to Trace, Debug, Info, Warn, Error, Fatal or Panic
|
|
||||||
Message string
|
|
||||||
|
|
||||||
// When formatter is called in entry.log(), a Buffer may be set to entry
|
|
||||||
Buffer *bytes.Buffer
|
|
||||||
|
|
||||||
// Contains the context set by the user. Useful for hook processing etc.
|
|
||||||
Context context.Context
|
|
||||||
|
|
||||||
// err may contain a field formatting error
|
|
||||||
err string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewEntry(logger *Logger) *Entry {
|
|
||||||
return &Entry{
|
|
||||||
Logger: logger,
|
|
||||||
// Default is three fields, plus one optional. Give a little extra room.
|
|
||||||
Data: make(Fields, 6),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the bytes representation of this entry from the formatter.
|
|
||||||
func (entry *Entry) Bytes() ([]byte, error) {
|
|
||||||
return entry.Logger.Formatter.Format(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the string representation from the reader and ultimately the
|
|
||||||
// formatter.
|
|
||||||
func (entry *Entry) String() (string, error) {
|
|
||||||
serialized, err := entry.Bytes()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
str := string(serialized)
|
|
||||||
return str, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add an error as single field (using the key defined in ErrorKey) to the Entry.
|
|
||||||
func (entry *Entry) WithError(err error) *Entry {
|
|
||||||
return entry.WithField(ErrorKey, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a context to the Entry.
|
|
||||||
func (entry *Entry) WithContext(ctx context.Context) *Entry {
|
|
||||||
dataCopy := make(Fields, len(entry.Data))
|
|
||||||
for k, v := range entry.Data {
|
|
||||||
dataCopy[k] = v
|
|
||||||
}
|
|
||||||
return &Entry{Logger: entry.Logger, Data: dataCopy, Time: entry.Time, err: entry.err, Context: ctx}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a single field to the Entry.
|
|
||||||
func (entry *Entry) WithField(key string, value interface{}) *Entry {
|
|
||||||
return entry.WithFields(Fields{key: value})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a map of fields to the Entry.
|
|
||||||
func (entry *Entry) WithFields(fields Fields) *Entry {
|
|
||||||
data := make(Fields, len(entry.Data)+len(fields))
|
|
||||||
for k, v := range entry.Data {
|
|
||||||
data[k] = v
|
|
||||||
}
|
|
||||||
fieldErr := entry.err
|
|
||||||
for k, v := range fields {
|
|
||||||
isErrField := false
|
|
||||||
if t := reflect.TypeOf(v); t != nil {
|
|
||||||
switch t.Kind() {
|
|
||||||
case reflect.Func:
|
|
||||||
isErrField = true
|
|
||||||
case reflect.Ptr:
|
|
||||||
isErrField = t.Elem().Kind() == reflect.Func
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if isErrField {
|
|
||||||
tmp := fmt.Sprintf("can not add field %q", k)
|
|
||||||
if fieldErr != "" {
|
|
||||||
fieldErr = entry.err + ", " + tmp
|
|
||||||
} else {
|
|
||||||
fieldErr = tmp
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
data[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: fieldErr, Context: entry.Context}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overrides the time of the Entry.
|
|
||||||
func (entry *Entry) WithTime(t time.Time) *Entry {
|
|
||||||
dataCopy := make(Fields, len(entry.Data))
|
|
||||||
for k, v := range entry.Data {
|
|
||||||
dataCopy[k] = v
|
|
||||||
}
|
|
||||||
return &Entry{Logger: entry.Logger, Data: dataCopy, Time: t, err: entry.err, Context: entry.Context}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPackageName reduces a fully qualified function name to the package name
|
|
||||||
// There really ought to be to be a better way...
|
|
||||||
func getPackageName(f string) string {
|
|
||||||
for {
|
|
||||||
lastPeriod := strings.LastIndex(f, ".")
|
|
||||||
lastSlash := strings.LastIndex(f, "/")
|
|
||||||
if lastPeriod > lastSlash {
|
|
||||||
f = f[:lastPeriod]
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
// getCaller retrieves the name of the first non-logrus calling function
|
|
||||||
func getCaller() *runtime.Frame {
|
|
||||||
// cache this package's fully-qualified name
|
|
||||||
callerInitOnce.Do(func() {
|
|
||||||
pcs := make([]uintptr, maximumCallerDepth)
|
|
||||||
_ = runtime.Callers(0, pcs)
|
|
||||||
|
|
||||||
// dynamic get the package name and the minimum caller depth
|
|
||||||
for i := 0; i < maximumCallerDepth; i++ {
|
|
||||||
funcName := runtime.FuncForPC(pcs[i]).Name()
|
|
||||||
if strings.Contains(funcName, "getCaller") {
|
|
||||||
logrusPackage = getPackageName(funcName)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
minimumCallerDepth = knownLogrusFrames
|
|
||||||
})
|
|
||||||
|
|
||||||
// Restrict the lookback frames to avoid runaway lookups
|
|
||||||
pcs := make([]uintptr, maximumCallerDepth)
|
|
||||||
depth := runtime.Callers(minimumCallerDepth, pcs)
|
|
||||||
frames := runtime.CallersFrames(pcs[:depth])
|
|
||||||
|
|
||||||
for f, again := frames.Next(); again; f, again = frames.Next() {
|
|
||||||
pkg := getPackageName(f.Function)
|
|
||||||
|
|
||||||
// If the caller isn't part of this package, we're done
|
|
||||||
if pkg != logrusPackage {
|
|
||||||
return &f //nolint:scopelint
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we got here, we failed to find the caller's context
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry Entry) HasCaller() (has bool) {
|
|
||||||
return entry.Logger != nil &&
|
|
||||||
entry.Logger.ReportCaller &&
|
|
||||||
entry.Caller != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function is not declared with a pointer value because otherwise
|
|
||||||
// race conditions will occur when using multiple goroutines
|
|
||||||
func (entry Entry) log(level Level, msg string) {
|
|
||||||
var buffer *bytes.Buffer
|
|
||||||
|
|
||||||
// Default to now, but allow users to override if they want.
|
|
||||||
//
|
|
||||||
// We don't have to worry about polluting future calls to Entry#log()
|
|
||||||
// with this assignment because this function is declared with a
|
|
||||||
// non-pointer receiver.
|
|
||||||
if entry.Time.IsZero() {
|
|
||||||
entry.Time = time.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.Level = level
|
|
||||||
entry.Message = msg
|
|
||||||
entry.Logger.mu.Lock()
|
|
||||||
if entry.Logger.ReportCaller {
|
|
||||||
entry.Caller = getCaller()
|
|
||||||
}
|
|
||||||
entry.Logger.mu.Unlock()
|
|
||||||
|
|
||||||
entry.fireHooks()
|
|
||||||
|
|
||||||
buffer = getBuffer()
|
|
||||||
defer func() {
|
|
||||||
entry.Buffer = nil
|
|
||||||
putBuffer(buffer)
|
|
||||||
}()
|
|
||||||
buffer.Reset()
|
|
||||||
entry.Buffer = buffer
|
|
||||||
|
|
||||||
entry.write()
|
|
||||||
|
|
||||||
entry.Buffer = nil
|
|
||||||
|
|
||||||
// To avoid Entry#log() returning a value that only would make sense for
|
|
||||||
// panic() to use in Entry#Panic(), we avoid the allocation by checking
|
|
||||||
// directly here.
|
|
||||||
if level <= PanicLevel {
|
|
||||||
panic(&entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) fireHooks() {
|
|
||||||
entry.Logger.mu.Lock()
|
|
||||||
defer entry.Logger.mu.Unlock()
|
|
||||||
err := entry.Logger.Hooks.Fire(entry.Level, entry)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) write() {
|
|
||||||
entry.Logger.mu.Lock()
|
|
||||||
defer entry.Logger.mu.Unlock()
|
|
||||||
serialized, err := entry.Logger.Formatter.Format(entry)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err = entry.Logger.Out.Write(serialized); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Log(level Level, args ...interface{}) {
|
|
||||||
if entry.Logger.IsLevelEnabled(level) {
|
|
||||||
entry.log(level, fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Trace(args ...interface{}) {
|
|
||||||
entry.Log(TraceLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Debug(args ...interface{}) {
|
|
||||||
entry.Log(DebugLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Print(args ...interface{}) {
|
|
||||||
entry.Info(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Info(args ...interface{}) {
|
|
||||||
entry.Log(InfoLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Warn(args ...interface{}) {
|
|
||||||
entry.Log(WarnLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Warning(args ...interface{}) {
|
|
||||||
entry.Warn(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Error(args ...interface{}) {
|
|
||||||
entry.Log(ErrorLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Fatal(args ...interface{}) {
|
|
||||||
entry.Log(FatalLevel, args...)
|
|
||||||
entry.Logger.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Panic(args ...interface{}) {
|
|
||||||
entry.Log(PanicLevel, args...)
|
|
||||||
panic(fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entry Printf family functions
|
|
||||||
|
|
||||||
func (entry *Entry) Logf(level Level, format string, args ...interface{}) {
|
|
||||||
if entry.Logger.IsLevelEnabled(level) {
|
|
||||||
entry.Log(level, fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Tracef(format string, args ...interface{}) {
|
|
||||||
entry.Logf(TraceLevel, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
|
||||||
entry.Logf(DebugLevel, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Infof(format string, args ...interface{}) {
|
|
||||||
entry.Logf(InfoLevel, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Printf(format string, args ...interface{}) {
|
|
||||||
entry.Infof(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
|
||||||
entry.Logf(WarnLevel, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Warningf(format string, args ...interface{}) {
|
|
||||||
entry.Warnf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
|
||||||
entry.Logf(ErrorLevel, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
|
||||||
entry.Logf(FatalLevel, format, args...)
|
|
||||||
entry.Logger.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
|
||||||
entry.Logf(PanicLevel, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entry Println family functions
|
|
||||||
|
|
||||||
func (entry *Entry) Logln(level Level, args ...interface{}) {
|
|
||||||
if entry.Logger.IsLevelEnabled(level) {
|
|
||||||
entry.Log(level, entry.sprintlnn(args...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Traceln(args ...interface{}) {
|
|
||||||
entry.Logln(TraceLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Debugln(args ...interface{}) {
|
|
||||||
entry.Logln(DebugLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Infoln(args ...interface{}) {
|
|
||||||
entry.Logln(InfoLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Println(args ...interface{}) {
|
|
||||||
entry.Infoln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Warnln(args ...interface{}) {
|
|
||||||
entry.Logln(WarnLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Warningln(args ...interface{}) {
|
|
||||||
entry.Warnln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Errorln(args ...interface{}) {
|
|
||||||
entry.Logln(ErrorLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Fatalln(args ...interface{}) {
|
|
||||||
entry.Logln(FatalLevel, args...)
|
|
||||||
entry.Logger.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Panicln(args ...interface{}) {
|
|
||||||
entry.Logln(PanicLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintlnn => Sprint no newline. This is to get the behavior of how
|
|
||||||
// fmt.Sprintln where spaces are always added between operands, regardless of
|
|
||||||
// their type. Instead of vendoring the Sprintln implementation to spare a
|
|
||||||
// string allocation, we do the simplest thing.
|
|
||||||
func (entry *Entry) sprintlnn(args ...interface{}) string {
|
|
||||||
msg := fmt.Sprintln(args...)
|
|
||||||
return msg[:len(msg)-1]
|
|
||||||
}
|
|
||||||
270
vendor/github.com/sirupsen/logrus/exported.go
generated
vendored
270
vendor/github.com/sirupsen/logrus/exported.go
generated
vendored
@@ -1,270 +0,0 @@
|
|||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// std is the name of the standard logger in stdlib `log`
|
|
||||||
std = New()
|
|
||||||
)
|
|
||||||
|
|
||||||
func StandardLogger() *Logger {
|
|
||||||
return std
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOutput sets the standard logger output.
|
|
||||||
func SetOutput(out io.Writer) {
|
|
||||||
std.SetOutput(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFormatter sets the standard logger formatter.
|
|
||||||
func SetFormatter(formatter Formatter) {
|
|
||||||
std.SetFormatter(formatter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetReportCaller sets whether the standard logger will include the calling
|
|
||||||
// method as a field.
|
|
||||||
func SetReportCaller(include bool) {
|
|
||||||
std.SetReportCaller(include)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLevel sets the standard logger level.
|
|
||||||
func SetLevel(level Level) {
|
|
||||||
std.SetLevel(level)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLevel returns the standard logger level.
|
|
||||||
func GetLevel() Level {
|
|
||||||
return std.GetLevel()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsLevelEnabled checks if the log level of the standard logger is greater than the level param
|
|
||||||
func IsLevelEnabled(level Level) bool {
|
|
||||||
return std.IsLevelEnabled(level)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddHook adds a hook to the standard logger hooks.
|
|
||||||
func AddHook(hook Hook) {
|
|
||||||
std.AddHook(hook)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
|
|
||||||
func WithError(err error) *Entry {
|
|
||||||
return std.WithField(ErrorKey, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithContext creates an entry from the standard logger and adds a context to it.
|
|
||||||
func WithContext(ctx context.Context) *Entry {
|
|
||||||
return std.WithContext(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithField creates an entry from the standard logger and adds a field to
|
|
||||||
// it. If you want multiple fields, use `WithFields`.
|
|
||||||
//
|
|
||||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
|
||||||
// or Panic on the Entry it returns.
|
|
||||||
func WithField(key string, value interface{}) *Entry {
|
|
||||||
return std.WithField(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithFields creates an entry from the standard logger and adds multiple
|
|
||||||
// fields to it. This is simply a helper for `WithField`, invoking it
|
|
||||||
// once for each field.
|
|
||||||
//
|
|
||||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
|
||||||
// or Panic on the Entry it returns.
|
|
||||||
func WithFields(fields Fields) *Entry {
|
|
||||||
return std.WithFields(fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithTime creates an entry from the standard logger and overrides the time of
|
|
||||||
// logs generated with it.
|
|
||||||
//
|
|
||||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
|
||||||
// or Panic on the Entry it returns.
|
|
||||||
func WithTime(t time.Time) *Entry {
|
|
||||||
return std.WithTime(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trace logs a message at level Trace on the standard logger.
|
|
||||||
func Trace(args ...interface{}) {
|
|
||||||
std.Trace(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug logs a message at level Debug on the standard logger.
|
|
||||||
func Debug(args ...interface{}) {
|
|
||||||
std.Debug(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print logs a message at level Info on the standard logger.
|
|
||||||
func Print(args ...interface{}) {
|
|
||||||
std.Print(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info logs a message at level Info on the standard logger.
|
|
||||||
func Info(args ...interface{}) {
|
|
||||||
std.Info(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warn logs a message at level Warn on the standard logger.
|
|
||||||
func Warn(args ...interface{}) {
|
|
||||||
std.Warn(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warning logs a message at level Warn on the standard logger.
|
|
||||||
func Warning(args ...interface{}) {
|
|
||||||
std.Warning(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error logs a message at level Error on the standard logger.
|
|
||||||
func Error(args ...interface{}) {
|
|
||||||
std.Error(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Panic logs a message at level Panic on the standard logger.
|
|
||||||
func Panic(args ...interface{}) {
|
|
||||||
std.Panic(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fatal logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
|
||||||
func Fatal(args ...interface{}) {
|
|
||||||
std.Fatal(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TraceFn logs a message from a func at level Trace on the standard logger.
|
|
||||||
func TraceFn(fn LogFunction) {
|
|
||||||
std.TraceFn(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DebugFn logs a message from a func at level Debug on the standard logger.
|
|
||||||
func DebugFn(fn LogFunction) {
|
|
||||||
std.DebugFn(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrintFn logs a message from a func at level Info on the standard logger.
|
|
||||||
func PrintFn(fn LogFunction) {
|
|
||||||
std.PrintFn(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InfoFn logs a message from a func at level Info on the standard logger.
|
|
||||||
func InfoFn(fn LogFunction) {
|
|
||||||
std.InfoFn(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WarnFn logs a message from a func at level Warn on the standard logger.
|
|
||||||
func WarnFn(fn LogFunction) {
|
|
||||||
std.WarnFn(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WarningFn logs a message from a func at level Warn on the standard logger.
|
|
||||||
func WarningFn(fn LogFunction) {
|
|
||||||
std.WarningFn(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorFn logs a message from a func at level Error on the standard logger.
|
|
||||||
func ErrorFn(fn LogFunction) {
|
|
||||||
std.ErrorFn(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PanicFn logs a message from a func at level Panic on the standard logger.
|
|
||||||
func PanicFn(fn LogFunction) {
|
|
||||||
std.PanicFn(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FatalFn logs a message from a func at level Fatal on the standard logger then the process will exit with status set to 1.
|
|
||||||
func FatalFn(fn LogFunction) {
|
|
||||||
std.FatalFn(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tracef logs a message at level Trace on the standard logger.
|
|
||||||
func Tracef(format string, args ...interface{}) {
|
|
||||||
std.Tracef(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debugf logs a message at level Debug on the standard logger.
|
|
||||||
func Debugf(format string, args ...interface{}) {
|
|
||||||
std.Debugf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf logs a message at level Info on the standard logger.
|
|
||||||
func Printf(format string, args ...interface{}) {
|
|
||||||
std.Printf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Infof logs a message at level Info on the standard logger.
|
|
||||||
func Infof(format string, args ...interface{}) {
|
|
||||||
std.Infof(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warnf logs a message at level Warn on the standard logger.
|
|
||||||
func Warnf(format string, args ...interface{}) {
|
|
||||||
std.Warnf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warningf logs a message at level Warn on the standard logger.
|
|
||||||
func Warningf(format string, args ...interface{}) {
|
|
||||||
std.Warningf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Errorf logs a message at level Error on the standard logger.
|
|
||||||
func Errorf(format string, args ...interface{}) {
|
|
||||||
std.Errorf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Panicf logs a message at level Panic on the standard logger.
|
|
||||||
func Panicf(format string, args ...interface{}) {
|
|
||||||
std.Panicf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fatalf logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
|
||||||
func Fatalf(format string, args ...interface{}) {
|
|
||||||
std.Fatalf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Traceln logs a message at level Trace on the standard logger.
|
|
||||||
func Traceln(args ...interface{}) {
|
|
||||||
std.Traceln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debugln logs a message at level Debug on the standard logger.
|
|
||||||
func Debugln(args ...interface{}) {
|
|
||||||
std.Debugln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Println logs a message at level Info on the standard logger.
|
|
||||||
func Println(args ...interface{}) {
|
|
||||||
std.Println(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Infoln logs a message at level Info on the standard logger.
|
|
||||||
func Infoln(args ...interface{}) {
|
|
||||||
std.Infoln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warnln logs a message at level Warn on the standard logger.
|
|
||||||
func Warnln(args ...interface{}) {
|
|
||||||
std.Warnln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warningln logs a message at level Warn on the standard logger.
|
|
||||||
func Warningln(args ...interface{}) {
|
|
||||||
std.Warningln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Errorln logs a message at level Error on the standard logger.
|
|
||||||
func Errorln(args ...interface{}) {
|
|
||||||
std.Errorln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Panicln logs a message at level Panic on the standard logger.
|
|
||||||
func Panicln(args ...interface{}) {
|
|
||||||
std.Panicln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fatalln logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
|
||||||
func Fatalln(args ...interface{}) {
|
|
||||||
std.Fatalln(args...)
|
|
||||||
}
|
|
||||||
78
vendor/github.com/sirupsen/logrus/formatter.go
generated
vendored
78
vendor/github.com/sirupsen/logrus/formatter.go
generated
vendored
@@ -1,78 +0,0 @@
|
|||||||
package logrus
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// Default key names for the default fields
|
|
||||||
const (
|
|
||||||
defaultTimestampFormat = time.RFC3339
|
|
||||||
FieldKeyMsg = "msg"
|
|
||||||
FieldKeyLevel = "level"
|
|
||||||
FieldKeyTime = "time"
|
|
||||||
FieldKeyLogrusError = "logrus_error"
|
|
||||||
FieldKeyFunc = "func"
|
|
||||||
FieldKeyFile = "file"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The Formatter interface is used to implement a custom Formatter. It takes an
|
|
||||||
// `Entry`. It exposes all the fields, including the default ones:
|
|
||||||
//
|
|
||||||
// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
|
|
||||||
// * `entry.Data["time"]`. The timestamp.
|
|
||||||
// * `entry.Data["level"]. The level the entry was logged at.
|
|
||||||
//
|
|
||||||
// Any additional fields added with `WithField` or `WithFields` are also in
|
|
||||||
// `entry.Data`. Format is expected to return an array of bytes which are then
|
|
||||||
// logged to `logger.Out`.
|
|
||||||
type Formatter interface {
|
|
||||||
Format(*Entry) ([]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is to not silently overwrite `time`, `msg`, `func` and `level` fields when
|
|
||||||
// dumping it. If this code wasn't there doing:
|
|
||||||
//
|
|
||||||
// logrus.WithField("level", 1).Info("hello")
|
|
||||||
//
|
|
||||||
// Would just silently drop the user provided level. Instead with this code
|
|
||||||
// it'll logged as:
|
|
||||||
//
|
|
||||||
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
|
|
||||||
//
|
|
||||||
// It's not exported because it's still using Data in an opinionated way. It's to
|
|
||||||
// avoid code duplication between the two default formatters.
|
|
||||||
func prefixFieldClashes(data Fields, fieldMap FieldMap, reportCaller bool) {
|
|
||||||
timeKey := fieldMap.resolve(FieldKeyTime)
|
|
||||||
if t, ok := data[timeKey]; ok {
|
|
||||||
data["fields."+timeKey] = t
|
|
||||||
delete(data, timeKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
msgKey := fieldMap.resolve(FieldKeyMsg)
|
|
||||||
if m, ok := data[msgKey]; ok {
|
|
||||||
data["fields."+msgKey] = m
|
|
||||||
delete(data, msgKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
levelKey := fieldMap.resolve(FieldKeyLevel)
|
|
||||||
if l, ok := data[levelKey]; ok {
|
|
||||||
data["fields."+levelKey] = l
|
|
||||||
delete(data, levelKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
logrusErrKey := fieldMap.resolve(FieldKeyLogrusError)
|
|
||||||
if l, ok := data[logrusErrKey]; ok {
|
|
||||||
data["fields."+logrusErrKey] = l
|
|
||||||
delete(data, logrusErrKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If reportCaller is not set, 'func' will not conflict.
|
|
||||||
if reportCaller {
|
|
||||||
funcKey := fieldMap.resolve(FieldKeyFunc)
|
|
||||||
if l, ok := data[funcKey]; ok {
|
|
||||||
data["fields."+funcKey] = l
|
|
||||||
}
|
|
||||||
fileKey := fieldMap.resolve(FieldKeyFile)
|
|
||||||
if l, ok := data[fileKey]; ok {
|
|
||||||
data["fields."+fileKey] = l
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
10
vendor/github.com/sirupsen/logrus/go.mod
generated
vendored
10
vendor/github.com/sirupsen/logrus/go.mod
generated
vendored
@@ -1,10 +0,0 @@
|
|||||||
module github.com/sirupsen/logrus
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
github.com/stretchr/testify v1.2.2
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037
|
|
||||||
)
|
|
||||||
|
|
||||||
go 1.13
|
|
||||||
10
vendor/github.com/sirupsen/logrus/go.sum
generated
vendored
10
vendor/github.com/sirupsen/logrus/go.sum
generated
vendored
@@ -1,10 +0,0 @@
|
|||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
|
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
34
vendor/github.com/sirupsen/logrus/hooks.go
generated
vendored
34
vendor/github.com/sirupsen/logrus/hooks.go
generated
vendored
@@ -1,34 +0,0 @@
|
|||||||
package logrus
|
|
||||||
|
|
||||||
// A hook to be fired when logging on the logging levels returned from
|
|
||||||
// `Levels()` on your implementation of the interface. Note that this is not
|
|
||||||
// fired in a goroutine or a channel with workers, you should handle such
|
|
||||||
// functionality yourself if your call is non-blocking and you don't wish for
|
|
||||||
// the logging calls for levels returned from `Levels()` to block.
|
|
||||||
type Hook interface {
|
|
||||||
Levels() []Level
|
|
||||||
Fire(*Entry) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal type for storing the hooks on a logger instance.
|
|
||||||
type LevelHooks map[Level][]Hook
|
|
||||||
|
|
||||||
// Add a hook to an instance of logger. This is called with
|
|
||||||
// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface.
|
|
||||||
func (hooks LevelHooks) Add(hook Hook) {
|
|
||||||
for _, level := range hook.Levels() {
|
|
||||||
hooks[level] = append(hooks[level], hook)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fire all the hooks for the passed level. Used by `entry.log` to fire
|
|
||||||
// appropriate hooks for a log entry.
|
|
||||||
func (hooks LevelHooks) Fire(level Level, entry *Entry) error {
|
|
||||||
for _, hook := range hooks[level] {
|
|
||||||
if err := hook.Fire(entry); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
125
vendor/github.com/sirupsen/logrus/json_formatter.go
generated
vendored
125
vendor/github.com/sirupsen/logrus/json_formatter.go
generated
vendored
@@ -1,125 +0,0 @@
|
|||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fieldKey string
|
|
||||||
|
|
||||||
// FieldMap allows customization of the key names for default fields.
|
|
||||||
type FieldMap map[fieldKey]string
|
|
||||||
|
|
||||||
func (f FieldMap) resolve(key fieldKey) string {
|
|
||||||
if k, ok := f[key]; ok {
|
|
||||||
return k
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSONFormatter formats logs into parsable json
|
|
||||||
type JSONFormatter struct {
|
|
||||||
// TimestampFormat sets the format used for marshaling timestamps.
|
|
||||||
TimestampFormat string
|
|
||||||
|
|
||||||
// DisableTimestamp allows disabling automatic timestamps in output
|
|
||||||
DisableTimestamp bool
|
|
||||||
|
|
||||||
// DisableHTMLEscape allows disabling html escaping in output
|
|
||||||
DisableHTMLEscape bool
|
|
||||||
|
|
||||||
// DataKey allows users to put all the log entry parameters into a nested dictionary at a given key.
|
|
||||||
DataKey string
|
|
||||||
|
|
||||||
// FieldMap allows users to customize the names of keys for default fields.
|
|
||||||
// As an example:
|
|
||||||
// formatter := &JSONFormatter{
|
|
||||||
// FieldMap: FieldMap{
|
|
||||||
// FieldKeyTime: "@timestamp",
|
|
||||||
// FieldKeyLevel: "@level",
|
|
||||||
// FieldKeyMsg: "@message",
|
|
||||||
// FieldKeyFunc: "@caller",
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
FieldMap FieldMap
|
|
||||||
|
|
||||||
// CallerPrettyfier can be set by the user to modify the content
|
|
||||||
// of the function and file keys in the json data when ReportCaller is
|
|
||||||
// activated. If any of the returned value is the empty string the
|
|
||||||
// corresponding key will be removed from json fields.
|
|
||||||
CallerPrettyfier func(*runtime.Frame) (function string, file string)
|
|
||||||
|
|
||||||
// PrettyPrint will indent all json logs
|
|
||||||
PrettyPrint bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format renders a single log entry
|
|
||||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
|
||||||
data := make(Fields, len(entry.Data)+4)
|
|
||||||
for k, v := range entry.Data {
|
|
||||||
switch v := v.(type) {
|
|
||||||
case error:
|
|
||||||
// Otherwise errors are ignored by `encoding/json`
|
|
||||||
// https://github.com/sirupsen/logrus/issues/137
|
|
||||||
data[k] = v.Error()
|
|
||||||
default:
|
|
||||||
data[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.DataKey != "" {
|
|
||||||
newData := make(Fields, 4)
|
|
||||||
newData[f.DataKey] = data
|
|
||||||
data = newData
|
|
||||||
}
|
|
||||||
|
|
||||||
prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
|
|
||||||
|
|
||||||
timestampFormat := f.TimestampFormat
|
|
||||||
if timestampFormat == "" {
|
|
||||||
timestampFormat = defaultTimestampFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
if entry.err != "" {
|
|
||||||
data[f.FieldMap.resolve(FieldKeyLogrusError)] = entry.err
|
|
||||||
}
|
|
||||||
if !f.DisableTimestamp {
|
|
||||||
data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat)
|
|
||||||
}
|
|
||||||
data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
|
|
||||||
data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
|
|
||||||
if entry.HasCaller() {
|
|
||||||
funcVal := entry.Caller.Function
|
|
||||||
fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
|
|
||||||
if f.CallerPrettyfier != nil {
|
|
||||||
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
|
|
||||||
}
|
|
||||||
if funcVal != "" {
|
|
||||||
data[f.FieldMap.resolve(FieldKeyFunc)] = funcVal
|
|
||||||
}
|
|
||||||
if fileVal != "" {
|
|
||||||
data[f.FieldMap.resolve(FieldKeyFile)] = fileVal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var b *bytes.Buffer
|
|
||||||
if entry.Buffer != nil {
|
|
||||||
b = entry.Buffer
|
|
||||||
} else {
|
|
||||||
b = &bytes.Buffer{}
|
|
||||||
}
|
|
||||||
|
|
||||||
encoder := json.NewEncoder(b)
|
|
||||||
encoder.SetEscapeHTML(!f.DisableHTMLEscape)
|
|
||||||
if f.PrettyPrint {
|
|
||||||
encoder.SetIndent("", " ")
|
|
||||||
}
|
|
||||||
if err := encoder.Encode(data); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to marshal fields to JSON, %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.Bytes(), nil
|
|
||||||
}
|
|
||||||
404
vendor/github.com/sirupsen/logrus/logger.go
generated
vendored
404
vendor/github.com/sirupsen/logrus/logger.go
generated
vendored
@@ -1,404 +0,0 @@
|
|||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LogFunction For big messages, it can be more efficient to pass a function
|
|
||||||
// and only call it if the log level is actually enables rather than
|
|
||||||
// generating the log message and then checking if the level is enabled
|
|
||||||
type LogFunction func()[]interface{}
|
|
||||||
|
|
||||||
type Logger struct {
|
|
||||||
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
|
|
||||||
// file, or leave it default which is `os.Stderr`. You can also set this to
|
|
||||||
// something more adventurous, such as logging to Kafka.
|
|
||||||
Out io.Writer
|
|
||||||
// Hooks for the logger instance. These allow firing events based on logging
|
|
||||||
// levels and log entries. For example, to send errors to an error tracking
|
|
||||||
// service, log to StatsD or dump the core on fatal errors.
|
|
||||||
Hooks LevelHooks
|
|
||||||
// All log entries pass through the formatter before logged to Out. The
|
|
||||||
// included formatters are `TextFormatter` and `JSONFormatter` for which
|
|
||||||
// TextFormatter is the default. In development (when a TTY is attached) it
|
|
||||||
// logs with colors, but to a file it wouldn't. You can easily implement your
|
|
||||||
// own that implements the `Formatter` interface, see the `README` or included
|
|
||||||
// formatters for examples.
|
|
||||||
Formatter Formatter
|
|
||||||
|
|
||||||
// Flag for whether to log caller info (off by default)
|
|
||||||
ReportCaller bool
|
|
||||||
|
|
||||||
// The logging level the logger should log at. This is typically (and defaults
|
|
||||||
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
|
||||||
// logged.
|
|
||||||
Level Level
|
|
||||||
// Used to sync writing to the log. Locking is enabled by Default
|
|
||||||
mu MutexWrap
|
|
||||||
// Reusable empty entry
|
|
||||||
entryPool sync.Pool
|
|
||||||
// Function to exit the application, defaults to `os.Exit()`
|
|
||||||
ExitFunc exitFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
type exitFunc func(int)
|
|
||||||
|
|
||||||
type MutexWrap struct {
|
|
||||||
lock sync.Mutex
|
|
||||||
disabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mw *MutexWrap) Lock() {
|
|
||||||
if !mw.disabled {
|
|
||||||
mw.lock.Lock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mw *MutexWrap) Unlock() {
|
|
||||||
if !mw.disabled {
|
|
||||||
mw.lock.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mw *MutexWrap) Disable() {
|
|
||||||
mw.disabled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a new logger. Configuration should be set by changing `Formatter`,
|
|
||||||
// `Out` and `Hooks` directly on the default logger instance. You can also just
|
|
||||||
// instantiate your own:
|
|
||||||
//
|
|
||||||
// var log = &logrus.Logger{
|
|
||||||
// Out: os.Stderr,
|
|
||||||
// Formatter: new(logrus.TextFormatter),
|
|
||||||
// Hooks: make(logrus.LevelHooks),
|
|
||||||
// Level: logrus.DebugLevel,
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// It's recommended to make this a global instance called `log`.
|
|
||||||
func New() *Logger {
|
|
||||||
return &Logger{
|
|
||||||
Out: os.Stderr,
|
|
||||||
Formatter: new(TextFormatter),
|
|
||||||
Hooks: make(LevelHooks),
|
|
||||||
Level: InfoLevel,
|
|
||||||
ExitFunc: os.Exit,
|
|
||||||
ReportCaller: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) newEntry() *Entry {
|
|
||||||
entry, ok := logger.entryPool.Get().(*Entry)
|
|
||||||
if ok {
|
|
||||||
return entry
|
|
||||||
}
|
|
||||||
return NewEntry(logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) releaseEntry(entry *Entry) {
|
|
||||||
entry.Data = map[string]interface{}{}
|
|
||||||
logger.entryPool.Put(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithField allocates a new entry and adds a field to it.
|
|
||||||
// Debug, Print, Info, Warn, Error, Fatal or Panic must be then applied to
|
|
||||||
// this new returned entry.
|
|
||||||
// If you want multiple fields, use `WithFields`.
|
|
||||||
func (logger *Logger) WithField(key string, value interface{}) *Entry {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
defer logger.releaseEntry(entry)
|
|
||||||
return entry.WithField(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adds a struct of fields to the log entry. All it does is call `WithField` for
|
|
||||||
// each `Field`.
|
|
||||||
func (logger *Logger) WithFields(fields Fields) *Entry {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
defer logger.releaseEntry(entry)
|
|
||||||
return entry.WithFields(fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add an error as single field to the log entry. All it does is call
|
|
||||||
// `WithError` for the given `error`.
|
|
||||||
func (logger *Logger) WithError(err error) *Entry {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
defer logger.releaseEntry(entry)
|
|
||||||
return entry.WithError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a context to the log entry.
|
|
||||||
func (logger *Logger) WithContext(ctx context.Context) *Entry {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
defer logger.releaseEntry(entry)
|
|
||||||
return entry.WithContext(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overrides the time of the log entry.
|
|
||||||
func (logger *Logger) WithTime(t time.Time) *Entry {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
defer logger.releaseEntry(entry)
|
|
||||||
return entry.WithTime(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Logf(level Level, format string, args ...interface{}) {
|
|
||||||
if logger.IsLevelEnabled(level) {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Logf(level, format, args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Tracef(format string, args ...interface{}) {
|
|
||||||
logger.Logf(TraceLevel, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
|
||||||
logger.Logf(DebugLevel, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Infof(format string, args ...interface{}) {
|
|
||||||
logger.Logf(InfoLevel, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Printf(format string, args ...interface{}) {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Printf(format, args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
|
||||||
logger.Logf(WarnLevel, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
|
||||||
logger.Warnf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
|
||||||
logger.Logf(ErrorLevel, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
|
||||||
logger.Logf(FatalLevel, format, args...)
|
|
||||||
logger.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
|
||||||
logger.Logf(PanicLevel, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Log(level Level, args ...interface{}) {
|
|
||||||
if logger.IsLevelEnabled(level) {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Log(level, args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) LogFn(level Level, fn LogFunction) {
|
|
||||||
if logger.IsLevelEnabled(level) {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Log(level, fn()...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Trace(args ...interface{}) {
|
|
||||||
logger.Log(TraceLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Debug(args ...interface{}) {
|
|
||||||
logger.Log(DebugLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Info(args ...interface{}) {
|
|
||||||
logger.Log(InfoLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Print(args ...interface{}) {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Print(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warn(args ...interface{}) {
|
|
||||||
logger.Log(WarnLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warning(args ...interface{}) {
|
|
||||||
logger.Warn(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Error(args ...interface{}) {
|
|
||||||
logger.Log(ErrorLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Fatal(args ...interface{}) {
|
|
||||||
logger.Log(FatalLevel, args...)
|
|
||||||
logger.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Panic(args ...interface{}) {
|
|
||||||
logger.Log(PanicLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) TraceFn(fn LogFunction) {
|
|
||||||
logger.LogFn(TraceLevel, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) DebugFn(fn LogFunction) {
|
|
||||||
logger.LogFn(DebugLevel, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) InfoFn(fn LogFunction) {
|
|
||||||
logger.LogFn(InfoLevel, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) PrintFn(fn LogFunction) {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Print(fn()...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) WarnFn(fn LogFunction) {
|
|
||||||
logger.LogFn(WarnLevel, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) WarningFn(fn LogFunction) {
|
|
||||||
logger.WarnFn(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) ErrorFn(fn LogFunction) {
|
|
||||||
logger.LogFn(ErrorLevel, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) FatalFn(fn LogFunction) {
|
|
||||||
logger.LogFn(FatalLevel, fn)
|
|
||||||
logger.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) PanicFn(fn LogFunction) {
|
|
||||||
logger.LogFn(PanicLevel, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Logln(level Level, args ...interface{}) {
|
|
||||||
if logger.IsLevelEnabled(level) {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Logln(level, args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Traceln(args ...interface{}) {
|
|
||||||
logger.Logln(TraceLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Debugln(args ...interface{}) {
|
|
||||||
logger.Logln(DebugLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Infoln(args ...interface{}) {
|
|
||||||
logger.Logln(InfoLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Println(args ...interface{}) {
|
|
||||||
entry := logger.newEntry()
|
|
||||||
entry.Println(args...)
|
|
||||||
logger.releaseEntry(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warnln(args ...interface{}) {
|
|
||||||
logger.Logln(WarnLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warningln(args ...interface{}) {
|
|
||||||
logger.Warnln(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Errorln(args ...interface{}) {
|
|
||||||
logger.Logln(ErrorLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Fatalln(args ...interface{}) {
|
|
||||||
logger.Logln(FatalLevel, args...)
|
|
||||||
logger.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Panicln(args ...interface{}) {
|
|
||||||
logger.Logln(PanicLevel, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Exit(code int) {
|
|
||||||
runHandlers()
|
|
||||||
if logger.ExitFunc == nil {
|
|
||||||
logger.ExitFunc = os.Exit
|
|
||||||
}
|
|
||||||
logger.ExitFunc(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
//When file is opened with appending mode, it's safe to
|
|
||||||
//write concurrently to a file (within 4k message on Linux).
|
|
||||||
//In these cases user can choose to disable the lock.
|
|
||||||
func (logger *Logger) SetNoLock() {
|
|
||||||
logger.mu.Disable()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) level() Level {
|
|
||||||
return Level(atomic.LoadUint32((*uint32)(&logger.Level)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLevel sets the logger level.
|
|
||||||
func (logger *Logger) SetLevel(level Level) {
|
|
||||||
atomic.StoreUint32((*uint32)(&logger.Level), uint32(level))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLevel returns the logger level.
|
|
||||||
func (logger *Logger) GetLevel() Level {
|
|
||||||
return logger.level()
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddHook adds a hook to the logger hooks.
|
|
||||||
func (logger *Logger) AddHook(hook Hook) {
|
|
||||||
logger.mu.Lock()
|
|
||||||
defer logger.mu.Unlock()
|
|
||||||
logger.Hooks.Add(hook)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsLevelEnabled checks if the log level of the logger is greater than the level param
|
|
||||||
func (logger *Logger) IsLevelEnabled(level Level) bool {
|
|
||||||
return logger.level() >= level
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFormatter sets the logger formatter.
|
|
||||||
func (logger *Logger) SetFormatter(formatter Formatter) {
|
|
||||||
logger.mu.Lock()
|
|
||||||
defer logger.mu.Unlock()
|
|
||||||
logger.Formatter = formatter
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOutput sets the logger output.
|
|
||||||
func (logger *Logger) SetOutput(output io.Writer) {
|
|
||||||
logger.mu.Lock()
|
|
||||||
defer logger.mu.Unlock()
|
|
||||||
logger.Out = output
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) SetReportCaller(reportCaller bool) {
|
|
||||||
logger.mu.Lock()
|
|
||||||
defer logger.mu.Unlock()
|
|
||||||
logger.ReportCaller = reportCaller
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReplaceHooks replaces the logger hooks and returns the old ones
|
|
||||||
func (logger *Logger) ReplaceHooks(hooks LevelHooks) LevelHooks {
|
|
||||||
logger.mu.Lock()
|
|
||||||
oldHooks := logger.Hooks
|
|
||||||
logger.Hooks = hooks
|
|
||||||
logger.mu.Unlock()
|
|
||||||
return oldHooks
|
|
||||||
}
|
|
||||||
186
vendor/github.com/sirupsen/logrus/logrus.go
generated
vendored
186
vendor/github.com/sirupsen/logrus/logrus.go
generated
vendored
@@ -1,186 +0,0 @@
|
|||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Fields type, used to pass to `WithFields`.
|
|
||||||
type Fields map[string]interface{}
|
|
||||||
|
|
||||||
// Level type
|
|
||||||
type Level uint32
|
|
||||||
|
|
||||||
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
|
|
||||||
func (level Level) String() string {
|
|
||||||
if b, err := level.MarshalText(); err == nil {
|
|
||||||
return string(b)
|
|
||||||
} else {
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseLevel takes a string level and returns the Logrus log level constant.
|
|
||||||
func ParseLevel(lvl string) (Level, error) {
|
|
||||||
switch strings.ToLower(lvl) {
|
|
||||||
case "panic":
|
|
||||||
return PanicLevel, nil
|
|
||||||
case "fatal":
|
|
||||||
return FatalLevel, nil
|
|
||||||
case "error":
|
|
||||||
return ErrorLevel, nil
|
|
||||||
case "warn", "warning":
|
|
||||||
return WarnLevel, nil
|
|
||||||
case "info":
|
|
||||||
return InfoLevel, nil
|
|
||||||
case "debug":
|
|
||||||
return DebugLevel, nil
|
|
||||||
case "trace":
|
|
||||||
return TraceLevel, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var l Level
|
|
||||||
return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
|
||||||
func (level *Level) UnmarshalText(text []byte) error {
|
|
||||||
l, err := ParseLevel(string(text))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
*level = l
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (level Level) MarshalText() ([]byte, error) {
|
|
||||||
switch level {
|
|
||||||
case TraceLevel:
|
|
||||||
return []byte("trace"), nil
|
|
||||||
case DebugLevel:
|
|
||||||
return []byte("debug"), nil
|
|
||||||
case InfoLevel:
|
|
||||||
return []byte("info"), nil
|
|
||||||
case WarnLevel:
|
|
||||||
return []byte("warning"), nil
|
|
||||||
case ErrorLevel:
|
|
||||||
return []byte("error"), nil
|
|
||||||
case FatalLevel:
|
|
||||||
return []byte("fatal"), nil
|
|
||||||
case PanicLevel:
|
|
||||||
return []byte("panic"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("not a valid logrus level %d", level)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A constant exposing all logging levels
|
|
||||||
var AllLevels = []Level{
|
|
||||||
PanicLevel,
|
|
||||||
FatalLevel,
|
|
||||||
ErrorLevel,
|
|
||||||
WarnLevel,
|
|
||||||
InfoLevel,
|
|
||||||
DebugLevel,
|
|
||||||
TraceLevel,
|
|
||||||
}
|
|
||||||
|
|
||||||
// These are the different logging levels. You can set the logging level to log
|
|
||||||
// on your instance of logger, obtained with `logrus.New()`.
|
|
||||||
const (
|
|
||||||
// PanicLevel level, highest level of severity. Logs and then calls panic with the
|
|
||||||
// message passed to Debug, Info, ...
|
|
||||||
PanicLevel Level = iota
|
|
||||||
// FatalLevel level. Logs and then calls `logger.Exit(1)`. It will exit even if the
|
|
||||||
// logging level is set to Panic.
|
|
||||||
FatalLevel
|
|
||||||
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
|
|
||||||
// Commonly used for hooks to send errors to an error tracking service.
|
|
||||||
ErrorLevel
|
|
||||||
// WarnLevel level. Non-critical entries that deserve eyes.
|
|
||||||
WarnLevel
|
|
||||||
// InfoLevel level. General operational entries about what's going on inside the
|
|
||||||
// application.
|
|
||||||
InfoLevel
|
|
||||||
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
|
|
||||||
DebugLevel
|
|
||||||
// TraceLevel level. Designates finer-grained informational events than the Debug.
|
|
||||||
TraceLevel
|
|
||||||
)
|
|
||||||
|
|
||||||
// Won't compile if StdLogger can't be realized by a log.Logger
|
|
||||||
var (
|
|
||||||
_ StdLogger = &log.Logger{}
|
|
||||||
_ StdLogger = &Entry{}
|
|
||||||
_ StdLogger = &Logger{}
|
|
||||||
)
|
|
||||||
|
|
||||||
// StdLogger is what your logrus-enabled library should take, that way
|
|
||||||
// it'll accept a stdlib logger and a logrus logger. There's no standard
|
|
||||||
// interface, this is the closest we get, unfortunately.
|
|
||||||
type StdLogger interface {
|
|
||||||
Print(...interface{})
|
|
||||||
Printf(string, ...interface{})
|
|
||||||
Println(...interface{})
|
|
||||||
|
|
||||||
Fatal(...interface{})
|
|
||||||
Fatalf(string, ...interface{})
|
|
||||||
Fatalln(...interface{})
|
|
||||||
|
|
||||||
Panic(...interface{})
|
|
||||||
Panicf(string, ...interface{})
|
|
||||||
Panicln(...interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// The FieldLogger interface generalizes the Entry and Logger types
|
|
||||||
type FieldLogger interface {
|
|
||||||
WithField(key string, value interface{}) *Entry
|
|
||||||
WithFields(fields Fields) *Entry
|
|
||||||
WithError(err error) *Entry
|
|
||||||
|
|
||||||
Debugf(format string, args ...interface{})
|
|
||||||
Infof(format string, args ...interface{})
|
|
||||||
Printf(format string, args ...interface{})
|
|
||||||
Warnf(format string, args ...interface{})
|
|
||||||
Warningf(format string, args ...interface{})
|
|
||||||
Errorf(format string, args ...interface{})
|
|
||||||
Fatalf(format string, args ...interface{})
|
|
||||||
Panicf(format string, args ...interface{})
|
|
||||||
|
|
||||||
Debug(args ...interface{})
|
|
||||||
Info(args ...interface{})
|
|
||||||
Print(args ...interface{})
|
|
||||||
Warn(args ...interface{})
|
|
||||||
Warning(args ...interface{})
|
|
||||||
Error(args ...interface{})
|
|
||||||
Fatal(args ...interface{})
|
|
||||||
Panic(args ...interface{})
|
|
||||||
|
|
||||||
Debugln(args ...interface{})
|
|
||||||
Infoln(args ...interface{})
|
|
||||||
Println(args ...interface{})
|
|
||||||
Warnln(args ...interface{})
|
|
||||||
Warningln(args ...interface{})
|
|
||||||
Errorln(args ...interface{})
|
|
||||||
Fatalln(args ...interface{})
|
|
||||||
Panicln(args ...interface{})
|
|
||||||
|
|
||||||
// IsDebugEnabled() bool
|
|
||||||
// IsInfoEnabled() bool
|
|
||||||
// IsWarnEnabled() bool
|
|
||||||
// IsErrorEnabled() bool
|
|
||||||
// IsFatalEnabled() bool
|
|
||||||
// IsPanicEnabled() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ext1FieldLogger (the first extension to FieldLogger) is superfluous, it is
|
|
||||||
// here for consistancy. Do not use. Use Logger or Entry instead.
|
|
||||||
type Ext1FieldLogger interface {
|
|
||||||
FieldLogger
|
|
||||||
Tracef(format string, args ...interface{})
|
|
||||||
Trace(args ...interface{})
|
|
||||||
Traceln(args ...interface{})
|
|
||||||
}
|
|
||||||
11
vendor/github.com/sirupsen/logrus/terminal_check_appengine.go
generated
vendored
11
vendor/github.com/sirupsen/logrus/terminal_check_appengine.go
generated
vendored
@@ -1,11 +0,0 @@
|
|||||||
// +build appengine
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
func checkIfTerminal(w io.Writer) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
13
vendor/github.com/sirupsen/logrus/terminal_check_bsd.go
generated
vendored
13
vendor/github.com/sirupsen/logrus/terminal_check_bsd.go
generated
vendored
@@ -1,13 +0,0 @@
|
|||||||
// +build darwin dragonfly freebsd netbsd openbsd
|
|
||||||
// +build !js
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
const ioctlReadTermios = unix.TIOCGETA
|
|
||||||
|
|
||||||
func isTerminal(fd int) bool {
|
|
||||||
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
7
vendor/github.com/sirupsen/logrus/terminal_check_js.go
generated
vendored
7
vendor/github.com/sirupsen/logrus/terminal_check_js.go
generated
vendored
@@ -1,7 +0,0 @@
|
|||||||
// +build js
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
func isTerminal(fd int) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
11
vendor/github.com/sirupsen/logrus/terminal_check_no_terminal.go
generated
vendored
11
vendor/github.com/sirupsen/logrus/terminal_check_no_terminal.go
generated
vendored
@@ -1,11 +0,0 @@
|
|||||||
// +build js nacl plan9
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
func checkIfTerminal(w io.Writer) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
17
vendor/github.com/sirupsen/logrus/terminal_check_notappengine.go
generated
vendored
17
vendor/github.com/sirupsen/logrus/terminal_check_notappengine.go
generated
vendored
@@ -1,17 +0,0 @@
|
|||||||
// +build !appengine,!js,!windows,!nacl,!plan9
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func checkIfTerminal(w io.Writer) bool {
|
|
||||||
switch v := w.(type) {
|
|
||||||
case *os.File:
|
|
||||||
return isTerminal(int(v.Fd()))
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
11
vendor/github.com/sirupsen/logrus/terminal_check_solaris.go
generated
vendored
11
vendor/github.com/sirupsen/logrus/terminal_check_solaris.go
generated
vendored
@@ -1,11 +0,0 @@
|
|||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
|
||||||
func isTerminal(fd int) bool {
|
|
||||||
_, err := unix.IoctlGetTermio(fd, unix.TCGETA)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
13
vendor/github.com/sirupsen/logrus/terminal_check_unix.go
generated
vendored
13
vendor/github.com/sirupsen/logrus/terminal_check_unix.go
generated
vendored
@@ -1,13 +0,0 @@
|
|||||||
// +build linux aix
|
|
||||||
// +build !js
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
const ioctlReadTermios = unix.TCGETS
|
|
||||||
|
|
||||||
func isTerminal(fd int) bool {
|
|
||||||
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
27
vendor/github.com/sirupsen/logrus/terminal_check_windows.go
generated
vendored
27
vendor/github.com/sirupsen/logrus/terminal_check_windows.go
generated
vendored
@@ -1,27 +0,0 @@
|
|||||||
// +build !appengine,!js,windows
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
func checkIfTerminal(w io.Writer) bool {
|
|
||||||
switch v := w.(type) {
|
|
||||||
case *os.File:
|
|
||||||
handle := windows.Handle(v.Fd())
|
|
||||||
var mode uint32
|
|
||||||
if err := windows.GetConsoleMode(handle, &mode); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
|
||||||
if err := windows.SetConsoleMode(handle, mode); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
334
vendor/github.com/sirupsen/logrus/text_formatter.go
generated
vendored
334
vendor/github.com/sirupsen/logrus/text_formatter.go
generated
vendored
@@ -1,334 +0,0 @@
|
|||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
red = 31
|
|
||||||
yellow = 33
|
|
||||||
blue = 36
|
|
||||||
gray = 37
|
|
||||||
)
|
|
||||||
|
|
||||||
var baseTimestamp time.Time
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
baseTimestamp = time.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TextFormatter formats logs into text
|
|
||||||
type TextFormatter struct {
|
|
||||||
// Set to true to bypass checking for a TTY before outputting colors.
|
|
||||||
ForceColors bool
|
|
||||||
|
|
||||||
// Force disabling colors.
|
|
||||||
DisableColors bool
|
|
||||||
|
|
||||||
// Force quoting of all values
|
|
||||||
ForceQuote bool
|
|
||||||
|
|
||||||
// DisableQuote disables quoting for all values.
|
|
||||||
// DisableQuote will have a lower priority than ForceQuote.
|
|
||||||
// If both of them are set to true, quote will be forced on all values.
|
|
||||||
DisableQuote bool
|
|
||||||
|
|
||||||
// Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/
|
|
||||||
EnvironmentOverrideColors bool
|
|
||||||
|
|
||||||
// Disable timestamp logging. useful when output is redirected to logging
|
|
||||||
// system that already adds timestamps.
|
|
||||||
DisableTimestamp bool
|
|
||||||
|
|
||||||
// Enable logging the full timestamp when a TTY is attached instead of just
|
|
||||||
// the time passed since beginning of execution.
|
|
||||||
FullTimestamp bool
|
|
||||||
|
|
||||||
// TimestampFormat to use for display when a full timestamp is printed
|
|
||||||
TimestampFormat string
|
|
||||||
|
|
||||||
// The fields are sorted by default for a consistent output. For applications
|
|
||||||
// that log extremely frequently and don't use the JSON formatter this may not
|
|
||||||
// be desired.
|
|
||||||
DisableSorting bool
|
|
||||||
|
|
||||||
// The keys sorting function, when uninitialized it uses sort.Strings.
|
|
||||||
SortingFunc func([]string)
|
|
||||||
|
|
||||||
// Disables the truncation of the level text to 4 characters.
|
|
||||||
DisableLevelTruncation bool
|
|
||||||
|
|
||||||
// PadLevelText Adds padding the level text so that all the levels output at the same length
|
|
||||||
// PadLevelText is a superset of the DisableLevelTruncation option
|
|
||||||
PadLevelText bool
|
|
||||||
|
|
||||||
// QuoteEmptyFields will wrap empty fields in quotes if true
|
|
||||||
QuoteEmptyFields bool
|
|
||||||
|
|
||||||
// Whether the logger's out is to a terminal
|
|
||||||
isTerminal bool
|
|
||||||
|
|
||||||
// FieldMap allows users to customize the names of keys for default fields.
|
|
||||||
// As an example:
|
|
||||||
// formatter := &TextFormatter{
|
|
||||||
// FieldMap: FieldMap{
|
|
||||||
// FieldKeyTime: "@timestamp",
|
|
||||||
// FieldKeyLevel: "@level",
|
|
||||||
// FieldKeyMsg: "@message"}}
|
|
||||||
FieldMap FieldMap
|
|
||||||
|
|
||||||
// CallerPrettyfier can be set by the user to modify the content
|
|
||||||
// of the function and file keys in the data when ReportCaller is
|
|
||||||
// activated. If any of the returned value is the empty string the
|
|
||||||
// corresponding key will be removed from fields.
|
|
||||||
CallerPrettyfier func(*runtime.Frame) (function string, file string)
|
|
||||||
|
|
||||||
terminalInitOnce sync.Once
|
|
||||||
|
|
||||||
// The max length of the level text, generated dynamically on init
|
|
||||||
levelTextMaxLength int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TextFormatter) init(entry *Entry) {
|
|
||||||
if entry.Logger != nil {
|
|
||||||
f.isTerminal = checkIfTerminal(entry.Logger.Out)
|
|
||||||
}
|
|
||||||
// Get the max length of the level text
|
|
||||||
for _, level := range AllLevels {
|
|
||||||
levelTextLength := utf8.RuneCount([]byte(level.String()))
|
|
||||||
if levelTextLength > f.levelTextMaxLength {
|
|
||||||
f.levelTextMaxLength = levelTextLength
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TextFormatter) isColored() bool {
|
|
||||||
isColored := f.ForceColors || (f.isTerminal && (runtime.GOOS != "windows"))
|
|
||||||
|
|
||||||
if f.EnvironmentOverrideColors {
|
|
||||||
switch force, ok := os.LookupEnv("CLICOLOR_FORCE"); {
|
|
||||||
case ok && force != "0":
|
|
||||||
isColored = true
|
|
||||||
case ok && force == "0", os.Getenv("CLICOLOR") == "0":
|
|
||||||
isColored = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return isColored && !f.DisableColors
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format renders a single log entry
|
|
||||||
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
|
||||||
data := make(Fields)
|
|
||||||
for k, v := range entry.Data {
|
|
||||||
data[k] = v
|
|
||||||
}
|
|
||||||
prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
|
|
||||||
keys := make([]string, 0, len(data))
|
|
||||||
for k := range data {
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
var funcVal, fileVal string
|
|
||||||
|
|
||||||
fixedKeys := make([]string, 0, 4+len(data))
|
|
||||||
if !f.DisableTimestamp {
|
|
||||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime))
|
|
||||||
}
|
|
||||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLevel))
|
|
||||||
if entry.Message != "" {
|
|
||||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyMsg))
|
|
||||||
}
|
|
||||||
if entry.err != "" {
|
|
||||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError))
|
|
||||||
}
|
|
||||||
if entry.HasCaller() {
|
|
||||||
if f.CallerPrettyfier != nil {
|
|
||||||
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
|
|
||||||
} else {
|
|
||||||
funcVal = entry.Caller.Function
|
|
||||||
fileVal = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
|
|
||||||
}
|
|
||||||
|
|
||||||
if funcVal != "" {
|
|
||||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFunc))
|
|
||||||
}
|
|
||||||
if fileVal != "" {
|
|
||||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFile))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !f.DisableSorting {
|
|
||||||
if f.SortingFunc == nil {
|
|
||||||
sort.Strings(keys)
|
|
||||||
fixedKeys = append(fixedKeys, keys...)
|
|
||||||
} else {
|
|
||||||
if !f.isColored() {
|
|
||||||
fixedKeys = append(fixedKeys, keys...)
|
|
||||||
f.SortingFunc(fixedKeys)
|
|
||||||
} else {
|
|
||||||
f.SortingFunc(keys)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fixedKeys = append(fixedKeys, keys...)
|
|
||||||
}
|
|
||||||
|
|
||||||
var b *bytes.Buffer
|
|
||||||
if entry.Buffer != nil {
|
|
||||||
b = entry.Buffer
|
|
||||||
} else {
|
|
||||||
b = &bytes.Buffer{}
|
|
||||||
}
|
|
||||||
|
|
||||||
f.terminalInitOnce.Do(func() { f.init(entry) })
|
|
||||||
|
|
||||||
timestampFormat := f.TimestampFormat
|
|
||||||
if timestampFormat == "" {
|
|
||||||
timestampFormat = defaultTimestampFormat
|
|
||||||
}
|
|
||||||
if f.isColored() {
|
|
||||||
f.printColored(b, entry, keys, data, timestampFormat)
|
|
||||||
} else {
|
|
||||||
|
|
||||||
for _, key := range fixedKeys {
|
|
||||||
var value interface{}
|
|
||||||
switch {
|
|
||||||
case key == f.FieldMap.resolve(FieldKeyTime):
|
|
||||||
value = entry.Time.Format(timestampFormat)
|
|
||||||
case key == f.FieldMap.resolve(FieldKeyLevel):
|
|
||||||
value = entry.Level.String()
|
|
||||||
case key == f.FieldMap.resolve(FieldKeyMsg):
|
|
||||||
value = entry.Message
|
|
||||||
case key == f.FieldMap.resolve(FieldKeyLogrusError):
|
|
||||||
value = entry.err
|
|
||||||
case key == f.FieldMap.resolve(FieldKeyFunc) && entry.HasCaller():
|
|
||||||
value = funcVal
|
|
||||||
case key == f.FieldMap.resolve(FieldKeyFile) && entry.HasCaller():
|
|
||||||
value = fileVal
|
|
||||||
default:
|
|
||||||
value = data[key]
|
|
||||||
}
|
|
||||||
f.appendKeyValue(b, key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.WriteByte('\n')
|
|
||||||
return b.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, data Fields, timestampFormat string) {
|
|
||||||
var levelColor int
|
|
||||||
switch entry.Level {
|
|
||||||
case DebugLevel, TraceLevel:
|
|
||||||
levelColor = gray
|
|
||||||
case WarnLevel:
|
|
||||||
levelColor = yellow
|
|
||||||
case ErrorLevel, FatalLevel, PanicLevel:
|
|
||||||
levelColor = red
|
|
||||||
default:
|
|
||||||
levelColor = blue
|
|
||||||
}
|
|
||||||
|
|
||||||
levelText := strings.ToUpper(entry.Level.String())
|
|
||||||
if !f.DisableLevelTruncation && !f.PadLevelText {
|
|
||||||
levelText = levelText[0:4]
|
|
||||||
}
|
|
||||||
if f.PadLevelText {
|
|
||||||
// Generates the format string used in the next line, for example "%-6s" or "%-7s".
|
|
||||||
// Based on the max level text length.
|
|
||||||
formatString := "%-" + strconv.Itoa(f.levelTextMaxLength) + "s"
|
|
||||||
// Formats the level text by appending spaces up to the max length, for example:
|
|
||||||
// - "INFO "
|
|
||||||
// - "WARNING"
|
|
||||||
levelText = fmt.Sprintf(formatString, levelText)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove a single newline if it already exists in the message to keep
|
|
||||||
// the behavior of logrus text_formatter the same as the stdlib log package
|
|
||||||
entry.Message = strings.TrimSuffix(entry.Message, "\n")
|
|
||||||
|
|
||||||
caller := ""
|
|
||||||
if entry.HasCaller() {
|
|
||||||
funcVal := fmt.Sprintf("%s()", entry.Caller.Function)
|
|
||||||
fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
|
|
||||||
|
|
||||||
if f.CallerPrettyfier != nil {
|
|
||||||
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
|
|
||||||
}
|
|
||||||
|
|
||||||
if fileVal == "" {
|
|
||||||
caller = funcVal
|
|
||||||
} else if funcVal == "" {
|
|
||||||
caller = fileVal
|
|
||||||
} else {
|
|
||||||
caller = fileVal + " " + funcVal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case f.DisableTimestamp:
|
|
||||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, entry.Message)
|
|
||||||
case !f.FullTimestamp:
|
|
||||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), caller, entry.Message)
|
|
||||||
default:
|
|
||||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s]%s %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), caller, entry.Message)
|
|
||||||
}
|
|
||||||
for _, k := range keys {
|
|
||||||
v := data[k]
|
|
||||||
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
|
|
||||||
f.appendValue(b, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TextFormatter) needsQuoting(text string) bool {
|
|
||||||
if f.ForceQuote {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if f.QuoteEmptyFields && len(text) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if f.DisableQuote {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, ch := range text {
|
|
||||||
if !((ch >= 'a' && ch <= 'z') ||
|
|
||||||
(ch >= 'A' && ch <= 'Z') ||
|
|
||||||
(ch >= '0' && ch <= '9') ||
|
|
||||||
ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
|
|
||||||
if b.Len() > 0 {
|
|
||||||
b.WriteByte(' ')
|
|
||||||
}
|
|
||||||
b.WriteString(key)
|
|
||||||
b.WriteByte('=')
|
|
||||||
f.appendValue(b, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
|
|
||||||
stringVal, ok := value.(string)
|
|
||||||
if !ok {
|
|
||||||
stringVal = fmt.Sprint(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !f.needsQuoting(stringVal) {
|
|
||||||
b.WriteString(stringVal)
|
|
||||||
} else {
|
|
||||||
b.WriteString(fmt.Sprintf("%q", stringVal))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
70
vendor/github.com/sirupsen/logrus/writer.go
generated
vendored
70
vendor/github.com/sirupsen/logrus/writer.go
generated
vendored
@@ -1,70 +0,0 @@
|
|||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"io"
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Writer at INFO level. See WriterLevel for details.
|
|
||||||
func (logger *Logger) Writer() *io.PipeWriter {
|
|
||||||
return logger.WriterLevel(InfoLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriterLevel returns an io.Writer that can be used to write arbitrary text to
|
|
||||||
// the logger at the given log level. Each line written to the writer will be
|
|
||||||
// printed in the usual way using formatters and hooks. The writer is part of an
|
|
||||||
// io.Pipe and it is the callers responsibility to close the writer when done.
|
|
||||||
// This can be used to override the standard library logger easily.
|
|
||||||
func (logger *Logger) WriterLevel(level Level) *io.PipeWriter {
|
|
||||||
return NewEntry(logger).WriterLevel(level)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) Writer() *io.PipeWriter {
|
|
||||||
return entry.WriterLevel(InfoLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) WriterLevel(level Level) *io.PipeWriter {
|
|
||||||
reader, writer := io.Pipe()
|
|
||||||
|
|
||||||
var printFunc func(args ...interface{})
|
|
||||||
|
|
||||||
switch level {
|
|
||||||
case TraceLevel:
|
|
||||||
printFunc = entry.Trace
|
|
||||||
case DebugLevel:
|
|
||||||
printFunc = entry.Debug
|
|
||||||
case InfoLevel:
|
|
||||||
printFunc = entry.Info
|
|
||||||
case WarnLevel:
|
|
||||||
printFunc = entry.Warn
|
|
||||||
case ErrorLevel:
|
|
||||||
printFunc = entry.Error
|
|
||||||
case FatalLevel:
|
|
||||||
printFunc = entry.Fatal
|
|
||||||
case PanicLevel:
|
|
||||||
printFunc = entry.Panic
|
|
||||||
default:
|
|
||||||
printFunc = entry.Print
|
|
||||||
}
|
|
||||||
|
|
||||||
go entry.writerScanner(reader, printFunc)
|
|
||||||
runtime.SetFinalizer(writer, writerFinalizer)
|
|
||||||
|
|
||||||
return writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) {
|
|
||||||
scanner := bufio.NewScanner(reader)
|
|
||||||
for scanner.Scan() {
|
|
||||||
printFunc(scanner.Text())
|
|
||||||
}
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
entry.Errorf("Error while reading from Writer: %s", err)
|
|
||||||
}
|
|
||||||
reader.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func writerFinalizer(writer *io.PipeWriter) {
|
|
||||||
writer.Close()
|
|
||||||
}
|
|
||||||
19
vendor/go.uber.org/atomic/.codecov.yml
generated
vendored
Normal file
19
vendor/go.uber.org/atomic/.codecov.yml
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
coverage:
|
||||||
|
range: 80..100
|
||||||
|
round: down
|
||||||
|
precision: 2
|
||||||
|
|
||||||
|
status:
|
||||||
|
project: # measuring the overall project coverage
|
||||||
|
default: # context, you can create multiple ones with custom titles
|
||||||
|
enabled: yes # must be yes|true to enable this status
|
||||||
|
target: 100 # specify the target coverage for each commit status
|
||||||
|
# option: "auto" (must increase from parent commit or pull request base)
|
||||||
|
# option: "X%" a static target percentage to hit
|
||||||
|
if_not_found: success # if parent is not found report status as success, error, or failure
|
||||||
|
if_ci_failed: error # if ci fails report status as success, error, or failure
|
||||||
|
|
||||||
|
# Also update COVER_IGNORE_PKGS in the Makefile.
|
||||||
|
ignore:
|
||||||
|
- /internal/gen-atomicint/
|
||||||
|
- /internal/gen-valuewrapper/
|
||||||
12
vendor/go.uber.org/atomic/.gitignore
generated
vendored
Normal file
12
vendor/go.uber.org/atomic/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/bin
|
||||||
|
.DS_Store
|
||||||
|
/vendor
|
||||||
|
cover.html
|
||||||
|
cover.out
|
||||||
|
lint.log
|
||||||
|
|
||||||
|
# Binaries
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Profiling output
|
||||||
|
*.prof
|
||||||
27
vendor/go.uber.org/atomic/.travis.yml
generated
vendored
Normal file
27
vendor/go.uber.org/atomic/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
sudo: false
|
||||||
|
language: go
|
||||||
|
go_import_path: go.uber.org/atomic
|
||||||
|
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- GO111MODULE=on
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- go: oldstable
|
||||||
|
- go: stable
|
||||||
|
env: LINT=1
|
||||||
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- vendor
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- go version
|
||||||
|
|
||||||
|
script:
|
||||||
|
- test -z "$LINT" || make lint
|
||||||
|
- make cover
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- bash <(curl -s https://codecov.io/bash)
|
||||||
76
vendor/go.uber.org/atomic/CHANGELOG.md
generated
vendored
Normal file
76
vendor/go.uber.org/atomic/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# Changelog
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.7.0] - 2020-09-14
|
||||||
|
### Added
|
||||||
|
- Support JSON serialization and deserialization of primitive atomic types.
|
||||||
|
- Support Text marshalling and unmarshalling for string atomics.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Disallow incorrect comparison of atomic values in a non-atomic way.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Remove dependency on `golang.org/x/{lint, tools}`.
|
||||||
|
|
||||||
|
## [1.6.0] - 2020-02-24
|
||||||
|
### Changed
|
||||||
|
- Drop library dependency on `golang.org/x/{lint, tools}`.
|
||||||
|
|
||||||
|
## [1.5.1] - 2019-11-19
|
||||||
|
- Fix bug where `Bool.CAS` and `Bool.Toggle` do work correctly together
|
||||||
|
causing `CAS` to fail even though the old value matches.
|
||||||
|
|
||||||
|
## [1.5.0] - 2019-10-29
|
||||||
|
### Changed
|
||||||
|
- With Go modules, only the `go.uber.org/atomic` import path is supported now.
|
||||||
|
If you need to use the old import path, please add a `replace` directive to
|
||||||
|
your `go.mod`.
|
||||||
|
|
||||||
|
## [1.4.0] - 2019-05-01
|
||||||
|
### Added
|
||||||
|
- Add `atomic.Error` type for atomic operations on `error` values.
|
||||||
|
|
||||||
|
## [1.3.2] - 2018-05-02
|
||||||
|
### Added
|
||||||
|
- Add `atomic.Duration` type for atomic operations on `time.Duration` values.
|
||||||
|
|
||||||
|
## [1.3.1] - 2017-11-14
|
||||||
|
### Fixed
|
||||||
|
- Revert optimization for `atomic.String.Store("")` which caused data races.
|
||||||
|
|
||||||
|
## [1.3.0] - 2017-11-13
|
||||||
|
### Added
|
||||||
|
- Add `atomic.Bool.CAS` for compare-and-swap semantics on bools.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Optimize `atomic.String.Store("")` by avoiding an allocation.
|
||||||
|
|
||||||
|
## [1.2.0] - 2017-04-12
|
||||||
|
### Added
|
||||||
|
- Shadow `atomic.Value` from `sync/atomic`.
|
||||||
|
|
||||||
|
## [1.1.0] - 2017-03-10
|
||||||
|
### Added
|
||||||
|
- Add atomic `Float64` type.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Support new `go.uber.org/atomic` import path.
|
||||||
|
|
||||||
|
## [1.0.0] - 2016-07-18
|
||||||
|
|
||||||
|
- Initial release.
|
||||||
|
|
||||||
|
[1.7.0]: https://github.com/uber-go/atomic/compare/v1.6.0...v1.7.0
|
||||||
|
[1.6.0]: https://github.com/uber-go/atomic/compare/v1.5.1...v1.6.0
|
||||||
|
[1.5.1]: https://github.com/uber-go/atomic/compare/v1.5.0...v1.5.1
|
||||||
|
[1.5.0]: https://github.com/uber-go/atomic/compare/v1.4.0...v1.5.0
|
||||||
|
[1.4.0]: https://github.com/uber-go/atomic/compare/v1.3.2...v1.4.0
|
||||||
|
[1.3.2]: https://github.com/uber-go/atomic/compare/v1.3.1...v1.3.2
|
||||||
|
[1.3.1]: https://github.com/uber-go/atomic/compare/v1.3.0...v1.3.1
|
||||||
|
[1.3.0]: https://github.com/uber-go/atomic/compare/v1.2.0...v1.3.0
|
||||||
|
[1.2.0]: https://github.com/uber-go/atomic/compare/v1.1.0...v1.2.0
|
||||||
|
[1.1.0]: https://github.com/uber-go/atomic/compare/v1.0.0...v1.1.0
|
||||||
|
[1.0.0]: https://github.com/uber-go/atomic/releases/tag/v1.0.0
|
||||||
19
vendor/go.uber.org/atomic/LICENSE.txt
generated
vendored
Normal file
19
vendor/go.uber.org/atomic/LICENSE.txt
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2016 Uber Technologies, Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
78
vendor/go.uber.org/atomic/Makefile
generated
vendored
Normal file
78
vendor/go.uber.org/atomic/Makefile
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# Directory to place `go install`ed binaries into.
|
||||||
|
export GOBIN ?= $(shell pwd)/bin
|
||||||
|
|
||||||
|
GOLINT = $(GOBIN)/golint
|
||||||
|
GEN_ATOMICINT = $(GOBIN)/gen-atomicint
|
||||||
|
GEN_ATOMICWRAPPER = $(GOBIN)/gen-atomicwrapper
|
||||||
|
STATICCHECK = $(GOBIN)/staticcheck
|
||||||
|
|
||||||
|
GO_FILES ?= $(shell find . '(' -path .git -o -path vendor ')' -prune -o -name '*.go' -print)
|
||||||
|
|
||||||
|
# Also update ignore section in .codecov.yml.
|
||||||
|
COVER_IGNORE_PKGS = \
|
||||||
|
go.uber.org/atomic/internal/gen-atomicint \
|
||||||
|
go.uber.org/atomic/internal/gen-atomicwrapper
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build:
|
||||||
|
go build ./...
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test:
|
||||||
|
go test -race ./...
|
||||||
|
|
||||||
|
.PHONY: gofmt
|
||||||
|
gofmt:
|
||||||
|
$(eval FMT_LOG := $(shell mktemp -t gofmt.XXXXX))
|
||||||
|
gofmt -e -s -l $(GO_FILES) > $(FMT_LOG) || true
|
||||||
|
@[ ! -s "$(FMT_LOG)" ] || (echo "gofmt failed:" && cat $(FMT_LOG) && false)
|
||||||
|
|
||||||
|
$(GOLINT):
|
||||||
|
cd tools && go install golang.org/x/lint/golint
|
||||||
|
|
||||||
|
$(STATICCHECK):
|
||||||
|
cd tools && go install honnef.co/go/tools/cmd/staticcheck
|
||||||
|
|
||||||
|
$(GEN_ATOMICWRAPPER): $(wildcard ./internal/gen-atomicwrapper/*)
|
||||||
|
go build -o $@ ./internal/gen-atomicwrapper
|
||||||
|
|
||||||
|
$(GEN_ATOMICINT): $(wildcard ./internal/gen-atomicint/*)
|
||||||
|
go build -o $@ ./internal/gen-atomicint
|
||||||
|
|
||||||
|
.PHONY: golint
|
||||||
|
golint: $(GOLINT)
|
||||||
|
$(GOLINT) ./...
|
||||||
|
|
||||||
|
.PHONY: staticcheck
|
||||||
|
staticcheck: $(STATICCHECK)
|
||||||
|
$(STATICCHECK) ./...
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
|
lint: gofmt golint staticcheck generatenodirty
|
||||||
|
|
||||||
|
# comma separated list of packages to consider for code coverage.
|
||||||
|
COVER_PKG = $(shell \
|
||||||
|
go list -find ./... | \
|
||||||
|
grep -v $(foreach pkg,$(COVER_IGNORE_PKGS),-e "^$(pkg)$$") | \
|
||||||
|
paste -sd, -)
|
||||||
|
|
||||||
|
.PHONY: cover
|
||||||
|
cover:
|
||||||
|
go test -coverprofile=cover.out -coverpkg $(COVER_PKG) -v ./...
|
||||||
|
go tool cover -html=cover.out -o cover.html
|
||||||
|
|
||||||
|
.PHONY: generate
|
||||||
|
generate: $(GEN_ATOMICINT) $(GEN_ATOMICWRAPPER)
|
||||||
|
go generate ./...
|
||||||
|
|
||||||
|
.PHONY: generatenodirty
|
||||||
|
generatenodirty:
|
||||||
|
@[ -z "$$(git status --porcelain)" ] || ( \
|
||||||
|
echo "Working tree is dirty. Commit your changes first."; \
|
||||||
|
exit 1 )
|
||||||
|
@make generate
|
||||||
|
@status=$$(git status --porcelain); \
|
||||||
|
[ -z "$$status" ] || ( \
|
||||||
|
echo "Working tree is dirty after `make generate`:"; \
|
||||||
|
echo "$$status"; \
|
||||||
|
echo "Please ensure that the generated code is up-to-date." )
|
||||||
63
vendor/go.uber.org/atomic/README.md
generated
vendored
Normal file
63
vendor/go.uber.org/atomic/README.md
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# atomic [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] [![Go Report Card][reportcard-img]][reportcard]
|
||||||
|
|
||||||
|
Simple wrappers for primitive types to enforce atomic access.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ go get -u go.uber.org/atomic@v1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Legacy Import Path
|
||||||
|
|
||||||
|
As of v1.5.0, the import path `go.uber.org/atomic` is the only supported way
|
||||||
|
of using this package. If you are using Go modules, this package will fail to
|
||||||
|
compile with the legacy import path path `github.com/uber-go/atomic`.
|
||||||
|
|
||||||
|
We recommend migrating your code to the new import path but if you're unable
|
||||||
|
to do so, or if your dependencies are still using the old import path, you
|
||||||
|
will have to add a `replace` directive to your `go.mod` file downgrading the
|
||||||
|
legacy import path to an older version.
|
||||||
|
|
||||||
|
```
|
||||||
|
replace github.com/uber-go/atomic => github.com/uber-go/atomic v1.4.0
|
||||||
|
```
|
||||||
|
|
||||||
|
You can do so automatically by running the following command.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ go mod edit -replace github.com/uber-go/atomic=github.com/uber-go/atomic@v1.4.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The standard library's `sync/atomic` is powerful, but it's easy to forget which
|
||||||
|
variables must be accessed atomically. `go.uber.org/atomic` preserves all the
|
||||||
|
functionality of the standard library, but wraps the primitive types to
|
||||||
|
provide a safer, more convenient API.
|
||||||
|
|
||||||
|
```go
|
||||||
|
var atom atomic.Uint32
|
||||||
|
atom.Store(42)
|
||||||
|
atom.Sub(2)
|
||||||
|
atom.CAS(40, 11)
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [documentation][doc] for a complete API specification.
|
||||||
|
|
||||||
|
## Development Status
|
||||||
|
|
||||||
|
Stable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Released under the [MIT License](LICENSE.txt).
|
||||||
|
|
||||||
|
[doc-img]: https://godoc.org/github.com/uber-go/atomic?status.svg
|
||||||
|
[doc]: https://godoc.org/go.uber.org/atomic
|
||||||
|
[ci-img]: https://travis-ci.com/uber-go/atomic.svg?branch=master
|
||||||
|
[ci]: https://travis-ci.com/uber-go/atomic
|
||||||
|
[cov-img]: https://codecov.io/gh/uber-go/atomic/branch/master/graph/badge.svg
|
||||||
|
[cov]: https://codecov.io/gh/uber-go/atomic
|
||||||
|
[reportcard-img]: https://goreportcard.com/badge/go.uber.org/atomic
|
||||||
|
[reportcard]: https://goreportcard.com/report/go.uber.org/atomic
|
||||||
81
vendor/go.uber.org/atomic/bool.go
generated
vendored
Normal file
81
vendor/go.uber.org/atomic/bool.go
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
// @generated Code generated by gen-atomicwrapper.
|
||||||
|
|
||||||
|
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package atomic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bool is an atomic type-safe wrapper for bool values.
|
||||||
|
type Bool struct {
|
||||||
|
_ nocmp // disallow non-atomic comparison
|
||||||
|
|
||||||
|
v Uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
var _zeroBool bool
|
||||||
|
|
||||||
|
// NewBool creates a new Bool.
|
||||||
|
func NewBool(v bool) *Bool {
|
||||||
|
x := &Bool{}
|
||||||
|
if v != _zeroBool {
|
||||||
|
x.Store(v)
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load atomically loads the wrapped bool.
|
||||||
|
func (x *Bool) Load() bool {
|
||||||
|
return truthy(x.v.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store atomically stores the passed bool.
|
||||||
|
func (x *Bool) Store(v bool) {
|
||||||
|
x.v.Store(boolToInt(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CAS is an atomic compare-and-swap for bool values.
|
||||||
|
func (x *Bool) CAS(o, n bool) bool {
|
||||||
|
return x.v.CAS(boolToInt(o), boolToInt(n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap atomically stores the given bool and returns the old
|
||||||
|
// value.
|
||||||
|
func (x *Bool) Swap(o bool) bool {
|
||||||
|
return truthy(x.v.Swap(boolToInt(o)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON encodes the wrapped bool into JSON.
|
||||||
|
func (x *Bool) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(x.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON decodes a bool from JSON.
|
||||||
|
func (x *Bool) UnmarshalJSON(b []byte) error {
|
||||||
|
var v bool
|
||||||
|
if err := json.Unmarshal(b, &v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
x.Store(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
53
vendor/go.uber.org/atomic/bool_ext.go
generated
vendored
Normal file
53
vendor/go.uber.org/atomic/bool_ext.go
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package atomic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate bin/gen-atomicwrapper -name=Bool -type=bool -wrapped=Uint32 -pack=boolToInt -unpack=truthy -cas -swap -json -file=bool.go
|
||||||
|
|
||||||
|
func truthy(n uint32) bool {
|
||||||
|
return n == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func boolToInt(b bool) uint32 {
|
||||||
|
if b {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle atomically negates the Boolean and returns the previous value.
|
||||||
|
func (b *Bool) Toggle() bool {
|
||||||
|
for {
|
||||||
|
old := b.Load()
|
||||||
|
if b.CAS(old, !old) {
|
||||||
|
return old
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String encodes the wrapped value as a string.
|
||||||
|
func (b *Bool) String() string {
|
||||||
|
return strconv.FormatBool(b.Load())
|
||||||
|
}
|
||||||
23
vendor/go.uber.org/atomic/doc.go
generated
vendored
Normal file
23
vendor/go.uber.org/atomic/doc.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
// Package atomic provides simple wrappers around numerics to enforce atomic
|
||||||
|
// access.
|
||||||
|
package atomic
|
||||||
82
vendor/go.uber.org/atomic/duration.go
generated
vendored
Normal file
82
vendor/go.uber.org/atomic/duration.go
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
// @generated Code generated by gen-atomicwrapper.
|
||||||
|
|
||||||
|
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package atomic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Duration is an atomic type-safe wrapper for time.Duration values.
|
||||||
|
type Duration struct {
|
||||||
|
_ nocmp // disallow non-atomic comparison
|
||||||
|
|
||||||
|
v Int64
|
||||||
|
}
|
||||||
|
|
||||||
|
var _zeroDuration time.Duration
|
||||||
|
|
||||||
|
// NewDuration creates a new Duration.
|
||||||
|
func NewDuration(v time.Duration) *Duration {
|
||||||
|
x := &Duration{}
|
||||||
|
if v != _zeroDuration {
|
||||||
|
x.Store(v)
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load atomically loads the wrapped time.Duration.
|
||||||
|
func (x *Duration) Load() time.Duration {
|
||||||
|
return time.Duration(x.v.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store atomically stores the passed time.Duration.
|
||||||
|
func (x *Duration) Store(v time.Duration) {
|
||||||
|
x.v.Store(int64(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CAS is an atomic compare-and-swap for time.Duration values.
|
||||||
|
func (x *Duration) CAS(o, n time.Duration) bool {
|
||||||
|
return x.v.CAS(int64(o), int64(n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap atomically stores the given time.Duration and returns the old
|
||||||
|
// value.
|
||||||
|
func (x *Duration) Swap(o time.Duration) time.Duration {
|
||||||
|
return time.Duration(x.v.Swap(int64(o)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON encodes the wrapped time.Duration into JSON.
|
||||||
|
func (x *Duration) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(x.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON decodes a time.Duration from JSON.
|
||||||
|
func (x *Duration) UnmarshalJSON(b []byte) error {
|
||||||
|
var v time.Duration
|
||||||
|
if err := json.Unmarshal(b, &v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
x.Store(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
40
vendor/go.uber.org/atomic/duration_ext.go
generated
vendored
Normal file
40
vendor/go.uber.org/atomic/duration_ext.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package atomic
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
//go:generate bin/gen-atomicwrapper -name=Duration -type=time.Duration -wrapped=Int64 -pack=int64 -unpack=time.Duration -cas -swap -json -imports time -file=duration.go
|
||||||
|
|
||||||
|
// Add atomically adds to the wrapped time.Duration and returns the new value.
|
||||||
|
func (d *Duration) Add(n time.Duration) time.Duration {
|
||||||
|
return time.Duration(d.v.Add(int64(n)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub atomically subtracts from the wrapped time.Duration and returns the new value.
|
||||||
|
func (d *Duration) Sub(n time.Duration) time.Duration {
|
||||||
|
return time.Duration(d.v.Sub(int64(n)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// String encodes the wrapped value as a string.
|
||||||
|
func (d *Duration) String() string {
|
||||||
|
return d.Load().String()
|
||||||
|
}
|
||||||
51
vendor/go.uber.org/atomic/error.go
generated
vendored
Normal file
51
vendor/go.uber.org/atomic/error.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// @generated Code generated by gen-atomicwrapper.
|
||||||
|
|
||||||
|
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package atomic
|
||||||
|
|
||||||
|
// Error is an atomic type-safe wrapper for error values.
|
||||||
|
type Error struct {
|
||||||
|
_ nocmp // disallow non-atomic comparison
|
||||||
|
|
||||||
|
v Value
|
||||||
|
}
|
||||||
|
|
||||||
|
var _zeroError error
|
||||||
|
|
||||||
|
// NewError creates a new Error.
|
||||||
|
func NewError(v error) *Error {
|
||||||
|
x := &Error{}
|
||||||
|
if v != _zeroError {
|
||||||
|
x.Store(v)
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load atomically loads the wrapped error.
|
||||||
|
func (x *Error) Load() error {
|
||||||
|
return unpackError(x.v.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store atomically stores the passed error.
|
||||||
|
func (x *Error) Store(v error) {
|
||||||
|
x.v.Store(packError(v))
|
||||||
|
}
|
||||||
39
vendor/go.uber.org/atomic/error_ext.go
generated
vendored
Normal file
39
vendor/go.uber.org/atomic/error_ext.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package atomic
|
||||||
|
|
||||||
|
// atomic.Value panics on nil inputs, or if the underlying type changes.
|
||||||
|
// Stabilize by always storing a custom struct that we control.
|
||||||
|
|
||||||
|
//go:generate bin/gen-atomicwrapper -name=Error -type=error -wrapped=Value -pack=packError -unpack=unpackError -file=error.go
|
||||||
|
|
||||||
|
type packedError struct{ Value error }
|
||||||
|
|
||||||
|
func packError(v error) interface{} {
|
||||||
|
return packedError{v}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unpackError(v interface{}) error {
|
||||||
|
if err, ok := v.(packedError); ok {
|
||||||
|
return err.Value
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
76
vendor/go.uber.org/atomic/float64.go
generated
vendored
Normal file
76
vendor/go.uber.org/atomic/float64.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
// @generated Code generated by gen-atomicwrapper.
|
||||||
|
|
||||||
|
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package atomic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Float64 is an atomic type-safe wrapper for float64 values.
|
||||||
|
type Float64 struct {
|
||||||
|
_ nocmp // disallow non-atomic comparison
|
||||||
|
|
||||||
|
v Uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
var _zeroFloat64 float64
|
||||||
|
|
||||||
|
// NewFloat64 creates a new Float64.
|
||||||
|
func NewFloat64(v float64) *Float64 {
|
||||||
|
x := &Float64{}
|
||||||
|
if v != _zeroFloat64 {
|
||||||
|
x.Store(v)
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load atomically loads the wrapped float64.
|
||||||
|
func (x *Float64) Load() float64 {
|
||||||
|
return math.Float64frombits(x.v.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store atomically stores the passed float64.
|
||||||
|
func (x *Float64) Store(v float64) {
|
||||||
|
x.v.Store(math.Float64bits(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CAS is an atomic compare-and-swap for float64 values.
|
||||||
|
func (x *Float64) CAS(o, n float64) bool {
|
||||||
|
return x.v.CAS(math.Float64bits(o), math.Float64bits(n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON encodes the wrapped float64 into JSON.
|
||||||
|
func (x *Float64) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(x.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON decodes a float64 from JSON.
|
||||||
|
func (x *Float64) UnmarshalJSON(b []byte) error {
|
||||||
|
var v float64
|
||||||
|
if err := json.Unmarshal(b, &v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
x.Store(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
47
vendor/go.uber.org/atomic/float64_ext.go
generated
vendored
Normal file
47
vendor/go.uber.org/atomic/float64_ext.go
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package atomic
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
//go:generate bin/gen-atomicwrapper -name=Float64 -type=float64 -wrapped=Uint64 -pack=math.Float64bits -unpack=math.Float64frombits -cas -json -imports math -file=float64.go
|
||||||
|
|
||||||
|
// Add atomically adds to the wrapped float64 and returns the new value.
|
||||||
|
func (f *Float64) Add(s float64) float64 {
|
||||||
|
for {
|
||||||
|
old := f.Load()
|
||||||
|
new := old + s
|
||||||
|
if f.CAS(old, new) {
|
||||||
|
return new
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub atomically subtracts from the wrapped float64 and returns the new value.
|
||||||
|
func (f *Float64) Sub(s float64) float64 {
|
||||||
|
return f.Add(-s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String encodes the wrapped value as a string.
|
||||||
|
func (f *Float64) String() string {
|
||||||
|
// 'g' is the behavior for floats with %v.
|
||||||
|
return strconv.FormatFloat(f.Load(), 'g', -1, 64)
|
||||||
|
}
|
||||||
26
vendor/go.uber.org/atomic/gen.go
generated
vendored
Normal file
26
vendor/go.uber.org/atomic/gen.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package atomic
|
||||||
|
|
||||||
|
//go:generate bin/gen-atomicint -name=Int32 -wrapped=int32 -file=int32.go
|
||||||
|
//go:generate bin/gen-atomicint -name=Int64 -wrapped=int64 -file=int64.go
|
||||||
|
//go:generate bin/gen-atomicint -name=Uint32 -wrapped=uint32 -unsigned -file=uint32.go
|
||||||
|
//go:generate bin/gen-atomicint -name=Uint64 -wrapped=uint64 -unsigned -file=uint64.go
|
||||||
102
vendor/go.uber.org/atomic/int32.go
generated
vendored
Normal file
102
vendor/go.uber.org/atomic/int32.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
// @generated Code generated by gen-atomicint.
|
||||||
|
|
||||||
|
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package atomic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Int32 is an atomic wrapper around int32.
|
||||||
|
type Int32 struct {
|
||||||
|
_ nocmp // disallow non-atomic comparison
|
||||||
|
|
||||||
|
v int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt32 creates a new Int32.
|
||||||
|
func NewInt32(i int32) *Int32 {
|
||||||
|
return &Int32{v: i}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load atomically loads the wrapped value.
|
||||||
|
func (i *Int32) Load() int32 {
|
||||||
|
return atomic.LoadInt32(&i.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add atomically adds to the wrapped int32 and returns the new value.
|
||||||
|
func (i *Int32) Add(n int32) int32 {
|
||||||
|
return atomic.AddInt32(&i.v, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub atomically subtracts from the wrapped int32 and returns the new value.
|
||||||
|
func (i *Int32) Sub(n int32) int32 {
|
||||||
|
return atomic.AddInt32(&i.v, -n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inc atomically increments the wrapped int32 and returns the new value.
|
||||||
|
func (i *Int32) Inc() int32 {
|
||||||
|
return i.Add(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dec atomically decrements the wrapped int32 and returns the new value.
|
||||||
|
func (i *Int32) Dec() int32 {
|
||||||
|
return i.Sub(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CAS is an atomic compare-and-swap.
|
||||||
|
func (i *Int32) CAS(old, new int32) bool {
|
||||||
|
return atomic.CompareAndSwapInt32(&i.v, old, new)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store atomically stores the passed value.
|
||||||
|
func (i *Int32) Store(n int32) {
|
||||||
|
atomic.StoreInt32(&i.v, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap atomically swaps the wrapped int32 and returns the old value.
|
||||||
|
func (i *Int32) Swap(n int32) int32 {
|
||||||
|
return atomic.SwapInt32(&i.v, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON encodes the wrapped int32 into JSON.
|
||||||
|
func (i *Int32) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(i.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON decodes JSON into the wrapped int32.
|
||||||
|
func (i *Int32) UnmarshalJSON(b []byte) error {
|
||||||
|
var v int32
|
||||||
|
if err := json.Unmarshal(b, &v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.Store(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String encodes the wrapped value as a string.
|
||||||
|
func (i *Int32) String() string {
|
||||||
|
v := i.Load()
|
||||||
|
return strconv.FormatInt(int64(v), 10)
|
||||||
|
}
|
||||||
102
vendor/go.uber.org/atomic/int64.go
generated
vendored
Normal file
102
vendor/go.uber.org/atomic/int64.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
// @generated Code generated by gen-atomicint.
|
||||||
|
|
||||||
|
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package atomic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Int64 is an atomic wrapper around int64.
|
||||||
|
type Int64 struct {
|
||||||
|
_ nocmp // disallow non-atomic comparison
|
||||||
|
|
||||||
|
v int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInt64 creates a new Int64.
|
||||||
|
func NewInt64(i int64) *Int64 {
|
||||||
|
return &Int64{v: i}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load atomically loads the wrapped value.
|
||||||
|
func (i *Int64) Load() int64 {
|
||||||
|
return atomic.LoadInt64(&i.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add atomically adds to the wrapped int64 and returns the new value.
|
||||||
|
func (i *Int64) Add(n int64) int64 {
|
||||||
|
return atomic.AddInt64(&i.v, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub atomically subtracts from the wrapped int64 and returns the new value.
|
||||||
|
func (i *Int64) Sub(n int64) int64 {
|
||||||
|
return atomic.AddInt64(&i.v, -n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inc atomically increments the wrapped int64 and returns the new value.
|
||||||
|
func (i *Int64) Inc() int64 {
|
||||||
|
return i.Add(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dec atomically decrements the wrapped int64 and returns the new value.
|
||||||
|
func (i *Int64) Dec() int64 {
|
||||||
|
return i.Sub(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CAS is an atomic compare-and-swap.
|
||||||
|
func (i *Int64) CAS(old, new int64) bool {
|
||||||
|
return atomic.CompareAndSwapInt64(&i.v, old, new)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store atomically stores the passed value.
|
||||||
|
func (i *Int64) Store(n int64) {
|
||||||
|
atomic.StoreInt64(&i.v, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap atomically swaps the wrapped int64 and returns the old value.
|
||||||
|
func (i *Int64) Swap(n int64) int64 {
|
||||||
|
return atomic.SwapInt64(&i.v, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON encodes the wrapped int64 into JSON.
|
||||||
|
func (i *Int64) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(i.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON decodes JSON into the wrapped int64.
|
||||||
|
func (i *Int64) UnmarshalJSON(b []byte) error {
|
||||||
|
var v int64
|
||||||
|
if err := json.Unmarshal(b, &v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.Store(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String encodes the wrapped value as a string.
|
||||||
|
func (i *Int64) String() string {
|
||||||
|
v := i.Load()
|
||||||
|
return strconv.FormatInt(int64(v), 10)
|
||||||
|
}
|
||||||
35
vendor/go.uber.org/atomic/nocmp.go
generated
vendored
Normal file
35
vendor/go.uber.org/atomic/nocmp.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package atomic
|
||||||
|
|
||||||
|
// nocmp is an uncomparable struct. Embed this inside another struct to make
|
||||||
|
// it uncomparable.
|
||||||
|
//
|
||||||
|
// type Foo struct {
|
||||||
|
// nocmp
|
||||||
|
// // ...
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// This DOES NOT:
|
||||||
|
//
|
||||||
|
// - Disallow shallow copies of structs
|
||||||
|
// - Disallow comparison of pointers to uncomparable structs
|
||||||
|
type nocmp [0]func()
|
||||||
54
vendor/go.uber.org/atomic/string.go
generated
vendored
Normal file
54
vendor/go.uber.org/atomic/string.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// @generated Code generated by gen-atomicwrapper.
|
||||||
|
|
||||||
|
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package atomic
|
||||||
|
|
||||||
|
// String is an atomic type-safe wrapper for string values.
|
||||||
|
type String struct {
|
||||||
|
_ nocmp // disallow non-atomic comparison
|
||||||
|
|
||||||
|
v Value
|
||||||
|
}
|
||||||
|
|
||||||
|
var _zeroString string
|
||||||
|
|
||||||
|
// NewString creates a new String.
|
||||||
|
func NewString(v string) *String {
|
||||||
|
x := &String{}
|
||||||
|
if v != _zeroString {
|
||||||
|
x.Store(v)
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load atomically loads the wrapped string.
|
||||||
|
func (x *String) Load() string {
|
||||||
|
if v := x.v.Load(); v != nil {
|
||||||
|
return v.(string)
|
||||||
|
}
|
||||||
|
return _zeroString
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store atomically stores the passed string.
|
||||||
|
func (x *String) Store(v string) {
|
||||||
|
x.v.Store(v)
|
||||||
|
}
|
||||||
43
vendor/go.uber.org/atomic/string_ext.go
generated
vendored
Normal file
43
vendor/go.uber.org/atomic/string_ext.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package atomic
|
||||||
|
|
||||||
|
//go:generate bin/gen-atomicwrapper -name=String -type=string -wrapped=Value -file=string.go
|
||||||
|
|
||||||
|
// String returns the wrapped value.
|
||||||
|
func (s *String) String() string {
|
||||||
|
return s.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalText encodes the wrapped string into a textual form.
|
||||||
|
//
|
||||||
|
// This makes it encodable as JSON, YAML, XML, and more.
|
||||||
|
func (s *String) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(s.Load()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText decodes text and replaces the wrapped string with it.
|
||||||
|
//
|
||||||
|
// This makes it decodable from JSON, YAML, XML, and more.
|
||||||
|
func (s *String) UnmarshalText(b []byte) error {
|
||||||
|
s.Store(string(b))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
102
vendor/go.uber.org/atomic/uint32.go
generated
vendored
Normal file
102
vendor/go.uber.org/atomic/uint32.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
// @generated Code generated by gen-atomicint.
|
||||||
|
|
||||||
|
// Copyright (c) 2020 Uber Technologies, Inc.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
package atomic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Uint32 is an atomic wrapper around uint32.
|
||||||
|
type Uint32 struct {
|
||||||
|
_ nocmp // disallow non-atomic comparison
|
||||||
|
|
||||||
|
v uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUint32 creates a new Uint32.
|
||||||
|
func NewUint32(i uint32) *Uint32 {
|
||||||
|
return &Uint32{v: i}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load atomically loads the wrapped value.
|
||||||
|
func (i *Uint32) Load() uint32 {
|
||||||
|
return atomic.LoadUint32(&i.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add atomically adds to the wrapped uint32 and returns the new value.
|
||||||
|
func (i *Uint32) Add(n uint32) uint32 {
|
||||||
|
return atomic.AddUint32(&i.v, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub atomically subtracts from the wrapped uint32 and returns the new value.
|
||||||
|
func (i *Uint32) Sub(n uint32) uint32 {
|
||||||
|
return atomic.AddUint32(&i.v, ^(n - 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inc atomically increments the wrapped uint32 and returns the new value.
|
||||||
|
func (i *Uint32) Inc() uint32 {
|
||||||
|
return i.Add(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dec atomically decrements the wrapped uint32 and returns the new value.
|
||||||
|
func (i *Uint32) Dec() uint32 {
|
||||||
|
return i.Sub(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CAS is an atomic compare-and-swap.
|
||||||
|
func (i *Uint32) CAS(old, new uint32) bool {
|
||||||
|
return atomic.CompareAndSwapUint32(&i.v, old, new)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store atomically stores the passed value.
|
||||||
|
func (i *Uint32) Store(n uint32) {
|
||||||
|
atomic.StoreUint32(&i.v, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap atomically swaps the wrapped uint32 and returns the old value.
|
||||||
|
func (i *Uint32) Swap(n uint32) uint32 {
|
||||||
|
return atomic.SwapUint32(&i.v, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON encodes the wrapped uint32 into JSON.
|
||||||
|
func (i *Uint32) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(i.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON decodes JSON into the wrapped uint32.
|
||||||
|
func (i *Uint32) UnmarshalJSON(b []byte) error {
|
||||||
|
var v uint32
|
||||||
|
if err := json.Unmarshal(b, &v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.Store(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String encodes the wrapped value as a string.
|
||||||
|
func (i *Uint32) String() string {
|
||||||
|
v := i.Load()
|
||||||
|
return strconv.FormatUint(uint64(v), 10)
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user